Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
(λx. From x Functions to x Types) Higher-Order Páli Gábor János [email protected] Eötvös Loránd University Department of Programming Languages and Compilers, Faculty of Informatics 3rd Winter School for PhD Students (WSPS3) January 16, 2016 Programming with Types in Haskell Levels of Type Definitions Type synonym. A different name for an already existing type of composition of such types. type Bool = Int Derived type. Definition of a type isomorphic to an already existing type. newtype Bool = B Int -- B :: Int -> Bool, a conversion function mkBool :: Int -> Bool -- smart constructor mkBool n | 0 <= n && n <= 1 = B n | otherwise = error "Invalid value" Algebraic Data Type (ADT). Makes it possible to control how the elements of the type can be constructed. data Bool = True | False -- True, False :: Bool, data constructors [3..47] Composing Custom Types Algebraically Every Haskell type can be expressed with the following types: Unit :: ? – unit (1) data Unit = Unit Pair :: ? → ? → ? – product (×) data Pair a b = Pair a b Either :: ? → ? → ? – sum or coproduct (+) data Either a b = Left a | Right b Fix :: (? → ?) → ? – fixed point (µ) data Fix f = Fix (f (Fix f)) An example of using all these combinators: List a = µ(λxs.1 + (a × xs)) type List a = Fix (L a) data L a xs = L (Either Unit (Pair a xs)) or more typically: data List a = Nil | Cons a (List a) [4..47] Types for Representing Information Algebraic data types can be used to model enumerations: data Bool = True | False (&&) :: Bool -> Bool -> Bool True && True = True _ && _ = False as well as data structures: data Tree a = L | B a (Tree a) (Tree a) aTree :: Tree Char aTree = B ’a’ (B ’b’ L (B ’c’ L L)) L size :: Tree a -> Integer size L = 0 size (B _ lt rt) = 1 + size lt + size rt [5..47] Types for Functions without Computation Meet the Peano numbers: Nat :: ? – N Zero :: Nat – 0 Succ :: Nat → Nat – S data Nat = Zero | Succ Nat aNat :: Nat aNat = Succ (Succ (Succ Zero))) -- S (S (S 0)) They do not immediately correspond to natural numbers, they have to be specifically interpreted like that: fromNat :: Nat -> Integer fromNat Zero = 0 fromNat (Succ n) = 1 + fromNat n aNat 6→∗β 3, but fromNat aNat →∗β 3 [6..47] Types for Hiding Functions Difference list is a function that returns a stored list appended to its parameter. data DiffList a = DFL ([a] -> [a]) It enables prepending and appending elements to the list in constant time. [7..47] Types for Hiding Functions -- http://hackage.haskell.org/package/dlist fromList :: [a] -> DiffList a fromList dl = DFL (\xs -> dl ++ xs) apply :: DiffList a -> [a] -> [a] apply (DFL f) xs = f xs toList :: DiffList a -> [a] toList df = apply df [] empty :: DiffList a empty = DFL (\xs -> xs) singleton :: a -> DiffList a singleton x = DFL (\xs -> x : xs) [8..47] Types for Hiding Functions infixr ‘cons‘ cons :: a -> DiffList a -> DiffList a x ‘cons‘ (DFL f) = DFL ((x:) . f) infixl ‘snoc‘ snoc :: DiffList a -> a -> DiffList a (DFL f) ‘snoc‘ x = DFL (f . (x:)) append :: DiffList a -> DiffList a -> DiffList a append (DFL f) (DFL g) = DFL (f . g) concat :: [DiffList a] -> DiffList a concat = Data.List.foldr append empty [9..47] Types for Hiding Functions replicate :: Int -> a -> DiffList a replicate n x = DFL (\xs -> let f m | m <= 0 = xs | otherwise = x : f (m - 1) in f n) foldr :: (a -> b -> b) -> b -> DiffList a -> b foldr f b = Data.List.foldr f b . toList map :: (a -> b) -> DiffList a -> DiffList b map f = foldr (cons . f) empty [10..47] Encoding Invariants with Types Alternating lists: data AList a b = Nil | Cons a (AList b a) alist :: AList Int Char alist = Cons 1 (Cons ’A’ (Cons 2 (Cons ’B’ Nil))) Cyclic lists: {-# LANGUAGE EmptyDataDecls #-} data Void data Maybe a = Nothing | Just a data CList a b = Var b | Nil 1 | C a (CList (Maybe b)) 1 2 2 3 clist1, clist2 :: CList Int Void clist1 = C 1 (C 2 (Var Nothing)) clist2 = C 1 (C 2 (C 3[11..47] (Var (Just Nothing)))) Typing with Types data Expr = ValI Integer | ValB Bool | Add Expr Expr | And Expr Expr | Eq Expr Expr | If Expr Expr Expr eval :: Expr -> Either Integer Bool eval (ValI n) = Left n eval (ValB b) = Right b eval (x ‘Add‘ y) = case (eval x, eval y) of (Left m , Left n) -> Left (m + n) eval (x ‘And‘ y) = case (eval x, eval y) of (Right a, Right b) -> Right (a && b) eval (x ‘Eq‘ y) = case (eval x, eval y) of (Left m , Left n) -> Right (m == n) (Right m, Right n) -> Right (m == n) eval (If x y z) = case (eval x) of Right b -> if b then (eval y) else (eval z) [12..47] Typing with Types Actually the Haskell compiler might be persuaded to do some static type checking for us: {-# LANGUAGE GADTs, KindSignatures #-} data Expr :: * -> * where ValI :: Integer -> Expr Integer ValB :: Bool -> Expr Bool Add :: Expr Integer -> Expr Integer -> Expr Integer And :: Expr Bool -> Expr Bool -> Expr Bool Eq :: (Eq a) => Expr a -> Expr a -> Expr Bool If :: Expr Bool -> Expr a -> Expr a -> Expr a eval eval eval eval eval eval eval :: (Eq a) => Expr a -> (ValI n) = n (ValB b) = b (x ‘Add‘ y) = (eval x) (x ‘And‘ y) = (eval x) (x ‘Eq‘ y) = (eval x) (If x y z) = if (eval a + (eval y) && (eval y) == (eval y) x) then (eval y) else (eval z) Hence eval becomes a total function. [13..47] Attaching Contexts with Types -- Data.Functor (Prelude) class Functor (f :: * -> *) where fmap :: (a -> b) -> (f a -> f b) -- Control.Applicative infixl 4 <$> (<$>) = fmap infixl 4 <*> class (Functor f) => Applicative (f :: * -> *) where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b x f Functor laws: Fx v f == == id fmap f . fmap g pure id <*> v pure (.) <*> u <*> v <*> w pure f <*> pure x u <*> pure y == == == == (identity) (composition) v u <*> (v <*> w) pure (f x) pure ($ y) <*> u [14..47] u z g y fmap id fmap (f . g) n m u z g y m n v (identity) (composition) (homomorphism) (interchange) Attaching Contexts with Types type Name = String type Env = [(Name, Integer)] newtype Expr a = E (Env -> a) var :: Name -> Expr Integer var n = E (\e -> case (lookup n e) of Just v -> v _ -> error ("variable is not defined: " ++ show n)) cond :: Expr Bool -> Expr a -> Expr a -> Expr a cond b x y = E (\e -> if (eval b e) then (eval x e) else (eval y e)) bind :: Name -> Expr Integer -> Expr a -> Expr a bind n x body = E (\e -> eval body ((n, eval x e) : e)) instance Functor Expr where fmap f x = pure f <*> x instance Applicative Expr where pure x = E (\_ -> x) f <*> x = E (\e -> (eval f e) (eval x e)) eval :: Expr a -> (Env -> a) eval (E expr) = expr [15..47] Attaching Contexts with Types gcd :: Expr Integer gcd = cond ((==) <$> var a <*> var b) (var a) (cond ((>) <$> var a <*> var b) (bind a ((-) <$> var a <*> var b) gcd) (bind b ((-) <$> var b <*> var a) gcd)) where [a,b] = ["a","b"] So, for example: eval gcd [("a", 113), ("b", 56)] →∗β 1 eval gcd [("a", 56), ("b", 98)] →∗β 14 eval gcd [("a", 42), ("b", 42)] →∗β 42 [16..47] Fear Not the Monad Fear Not the Monad: Introduction The bad news: Understading monads are a must to speak contemporal Haskell. The good news: They are not that complicated at all! [18..47] Fear Not the Monad: Introduction The number of monad tutorials over the years https://byorgey.wordpress.com/2009/01/12/ abstraction-intuition-and-the-monad-tutorial-fallacy/ [19..47] Fear Not the Burrito [20..47] Fear Not the Burrito [21..47] Fear Not the Burrito [22..47] Fear Not the Burrito [23..47] Fear Not the Burrito [24..47] Fear Not the Burrito [25..47] Fear Not the Burrito [26..47] What a Monad is not? All of the following claims is false: I Monads are not purely functional. I Monads model effects in programs. I Monads are about state. I Monads implement sequencing statements. I Monads model I/O. I Monads need lazy evaluation. I Monads help us to use tricky side effects. I Monads are an embedded language in Haskell. I Monads can be only comprehended by mathematicians. [27..47] Understanding the Monads in 8 Simple Steps 1. Do not read monad tutorials. 2. Do not read monad tutorials. 3. Learn about Haskell types. 4. Learn to use type classes. 5. Study the Typeclassopedia∗ . 6. Study the definition of monads. 7. Program with monads. 8. Do not write monad tutorials. ∗ http://www.cs.tufts.edu/comp/150FP/archive/ brent-yorgey/tc.pdf [28..47] Programming with Monads: Structuring Programs Assume that we would like to implement the following program in a purely functional language: I Print to the console that "Provide me a word> ". I Read the input from the user. Depending of the value of the input: I I I If it is a palindrome, print that "Palindrome." If it is not a palindrome, print that "Not a palindrome." Given: putStr :: String -> World -> World getLine :: World -> (String, World) [29..47] Programming with Monads: Structuring Programs ($) :: (a -> b) -> a -> b f $ x = f x program :: World -> World program w = (\(s, w) -> if (s == reverse s) then putStr "A palindrome." w else putStr "Not a palindrome." w) $ getLine $ putStr "Provide me a word> " $ w [30..47] Programming with Monads: Structuring Programs (|>) :: a -> (a -> b) -> b x |> f = f x program’ :: World -> World program’ w = w |> putStr "Provide me a word> " |> getLine |> \(s, w) -> if (s == reverse s) then putStr "A palindrome." w else putStr "Not a palindrome." w [31..47] Programming with Monads: Structuring Programs type IO a = World -> (a, World) -- putStr :: String -> IO () -- getLine :: IO String (>>=) :: IO a -> (a -> IO b) -> IO b (f >>= g) w = (g x) w’ where (x, w’) = f w (>>) :: IO a -> IO b -> IO b f >> g = f >>= \_ -> g program’’ :: IO () program’’ = putStr "Provide me a word> " >> getLine >>= \s -> if (s == reverse s) then putStr "A palindrome." else putStr "Not a palindrome." [32..47] Programming with Monads: Structuring Programs main :: IO () main = do putStr "Provide me a word> " s <- getLine if (s == reverse s) then putStr "A palindrome." else putStr "Not a palindrome." [33..47] Programming with Monads: The Monad Class -- Control.Monad class Monad (m :: * -> *) where return :: a -> m a (>>=) :: m a -> (a -> m b) -> m b (>>) :: m a -> m b -> m b x >> f = x >>= \_ -> f fail :: String -> m a fail s = error s (>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c) f >=> g = \x -> f x >>= g return >=> f f >=> return (f >=> g) >=> h === === === f f f >=> (g >=> h) + a run function [34..47] Programming with Monads: The do Syntax Rewriting rules: do { e1; e2 } --> e1 >> do { e2 } do { p <- e1; e2 } --> e1 >>= \x -> case x of p -> do { e2 } _ -> fail "error" do { let p = e1; e2 } --> let p = e1 in do { e2 } do { e } --> e For example: do { do x <- f; x <- f y <- g; y <- g z <- h; --> z <- h --> let t = (x,y,z); let t = (x,y,z) return t return t } [35..47] f >>= \x -> g >>= \y -> h >>= \z -> let t = (x,y,z) in return t Every Monad is a Functor Consider the following connection between functors and monads: join :: Monad m => m (m a) -> m a join x = x >>= id x >>= f = join (fmap f x) fmap f pure x mf <*> do { pure f do { fmap x === pure f <*> x === return x mx === f <- mf; x <- mx; return (f x) } <*> mx === f’ <- return f; x <- mx; return (f’ x) } === f x Starting from GHC 7.10, it is mandatory to make any monad a functor. [36..47] IO, the Most Infamous Monad data IO a = ? With the IO monad, it is possible to model arbitrary side effect, that is a "jolly joker". For example: I putStrLn: Print to the standard output. I getLine: Read from the standard input. I IORef: Mutable references, "pointers". I IOError: Exception handling. I IOArray: Efficient, mutable arrays. I forkIO: Concurrent threads. The run function: the run-time system, unsafePerformIO [37..47] State: Stateful Variables (Intuition) type State s a = s -> (a, s) -(s -> (a, s)) -> (a -> (s -> (b, s))) -> (s -> (b, s)) (>>=) :: State s a -> (a -> State s b) -> State s b (x >>= f) s = (f v) s’ where (v, s’) = x s -a -> (s -> (a, s)) return :: a -> State s a (return x) s = (x, s) -s -> (s, s) get :: State s s (get) s = (s, s) -s -> ((), s) put :: s -> State s () (put x) s = ((), x) -(s -> (a, s)) -> s -> (a, s)) runState :: State s a -> s -> (a, s)) runState f s = f s [38..47] State: Stateful Variables -- Control.Monad.State newtype State s a = S (s -> (a, s)) runState :: State s a -> s -> (a, s) runState (S f) x = f x instance Monad (State s) where return x = S (\s -> (x,s)) x >>= f = S (\s -> let (x’, s’) = runState x s in runState (f x’) s’) get :: State s s get = S (\s -> (s, s)) put :: s -> State s () put x = S (\_ -> ((), x)) [39..47] State: Stateful Variables (Example) data Tree a = Node a (Tree a) (Tree a) | Leaf deriving Show numberTree :: (Eq a) => Tree a -> Tree Int numberTree t = fst (runState (numberTreeM t) []) numberTreeM :: (Eq a) => Tree a -> State [a] (Tree Int) numberTreeM Leaf = return Leaf numberTreeM (Node x lt rt) = do table <- get let (table’, pos) = case (Data.List.findIndex (== x) table) of Just i -> (table, i) _ -> (table ++ [x], length table) put table’ lt’ <- numberTreeM lt rt’ <- numberTreeM rt return (Node pos lt’ rt’) [40..47] Maybe: Optional Values data Maybe a = Just a | Nothing instance Monad Maybe where return x = Just x (Just x) >>= f = f x _ >>= _ = Nothing fail _ = Nothing For example: safeDiv :: Integer -> Integer -> Maybe Integer _ ‘safeDiv‘ 0 = Nothing x ‘safeDiv‘ y = Just (x ‘div‘ y) averageDeviation :: [Integer] -> Maybe Integer averageDeviation xs = do let average xs = (sum xs) ‘safeDiv‘ (toInteger $ length xs) mean <- average xs average [ abs (x - mean) | x <- xs ] [41..47] List: Nondeterminism data [a] = [] | a:[a] instance Monad [] where return x = [x] xs >>= f = concat [ f x | x <- xs ] fail _ = [] For example: multiplyToNM :: Integer -> [(Integer, Integer)] multiplyToNM n = do x <- [1..n] y <- [x..n] if (x * y == n) then return (x,y) else fail "Not applicable." multiplyToN n = [ (x,y) | x <- [1..n], y <- [x..n], x * y == n ] [42..47] Writer: Logging -- Control.Monad.Writer newtype Writer w a = W (a, w) runWriter :: (Monoid w) => Writer w a -> (a, w) runWriter (W (x, w)) = (x, w) instance (Monoid w) => Monad (Writer w) where return x = W (x, mempty) x >>= f = W (let (x’, w) = runWriter x in let (x’’, w’) = runWriter (f x’) in (x’’, w <> w’)) tell :: (Monoid w) => w -> Writer w () tell x = W ((), x) censor :: (Monoid w) => (w -> w) -> Writer w a -> Writer w a censor f (W (x, w)) = W (x, f w) [43..47] Writer: Logging with Monoid -- Data.Monoid class Monoid (a :: *) where mempty :: a mappend :: a -> a -> a (<>) :: (Monoid a) => a -> a -> a (<>) = mappend -- GHC 7.6+ Monoid laws: mempty <> x x <> mempty x <> (y <> z) mconcat === === === === x x (x <> y) <> z foldr (<>) mempty For example: instance Monoid [a] where mempty = [] mappend = (++) -- data Sum a = Sum a instance (Num a) => Monoid (Sum a) where mempty = Sum 0 (Sum x) ‘mappend‘ (Sum y) = Sum (x + y) [44..47] Writer: Logging (Example) gcdWithLog :: Int -> Int -> (Int, [String]) gcdWithLog x y = runWriter (censor reverse (gcdWithLogM x y)) gcdWithLogM :: Int -> Int -> Writer [String] Int gcdWithLogM x y | y == 0 = do tell ["Finished with " ++ show x] return x gcdWithLogM x y = do result <- gcdWithLogM y (x ‘mod‘ y) let msg = show x ++ " mod " ++ show y ++ " = " ++ show (x ‘mod‘ y) tell [msg] return result [45..47] Composing Monads – Monad Transformers countEntries :: FilePath -> K () countEntries path = f path 0 where f p n = do deepestReached <- get when (deepestReached < n) (put n) c <- liftIO (System.Directory.getDirectoryContents p) let contents = filter (‘notElem‘ [".", ".."]) c tell [(p, length contents)] forM_ contents (\name -> do let newName = p ++ "/" ++ name isDir <- liftIO (System.Directory.doesDirectoryExist newName) maxDepth <- ask when (isDir && (n < maxDepth)) (f newName (n + 1))) type S = Int type C = Int type K = ReaderT C (StateT S (WriterT [(FilePath,Int)] IO)) runStack :: K a -> Int -> IO ((a,S),[(FilePath,Int)]) runStack k max = runWriterT (runStateT (runReaderT k c) s) where (s, c) = (0, max) [46..47] Stacking Monads [47..47]