10 21 08
play

10/21/08 cs242 Monads for functional programming uses unit - PDF document

10/21/08 cs242 Monads for functional programming uses unit instead of return instead of >>= But it is talking about the same things. Kathleen Fisher Real World Haskell, Chapter 14, uses


  1. 10/21/08
 cs242 �  “Monads for functional programming” uses �  unit instead of return  ★ instead of >>= But it is talking about the same things. � Kathleen Fisher �  “Real World Haskell”, Chapter 14, uses running examples introduced in previous chapters. You don’ t need to understand all Reading: “A history of Haskell: Being lazy with class”, � Section 6.4 and Section 7 � that code, just the big picture. � “Monads for functional programming” � � Sections 1-3 � � “Real World Haskell”, Chapter 14: Monads � Thanks to Andrew Tolmach and Simon Peyton Jones for some of these slides. �  Basic actions in IO monad have “side effects”: �  The special notation � getChar :: IO Char putChar :: Char -> IO () do {v1 <- e1; e2} isEOF :: IO Bool is “syntactic” sugar for the ordinary expression �  “Do” combines actions into larger actions: � e1 >>= \v1 -> e2 echo :: IO () where >>= (called bind) sequences actions. � echo = do { b <- isEOF; if not b then do (>>=) :: IO a -> (a -> IO b) -> IO b { x <- getChar; putChar x; echo } else return () }  The value returned by the first action needs  Operations happen only at the “top level” where to be fed to the second; hence the 2 nd arg to we implicitly perform an operation with type � >>= is a function (often an explicit lambda). � runIO :: IO a -> a -- Doesn’t really exist  Pure functional languages make all data flow  Actions of type IO() don’ t carry a useful explicit. � value, so we can sequence them with >> . �  Advantages � (>>) :: IO a -> IO b -> IO b  Value of an expression depends only on its free e1 >> e2 = e1 >>= (\_ -> e2) variables, making equational reasoning valid. �  The full translation for “do” notation is: �  Order of evaluation is irrelevant, so programs may be evaluated lazily. � do { x<-e; es } � = � e >>= \x -> do { es } �  Modularity: everything is explicitly named, so do { e; es } � = � e >> do { es } � programmer has maximum flexibility. �  Disadvantages � do { e } � = � e �  Plumbing, plumbing, plumbing! � do {let do {l t ds ds; ; es es} = l } = let t ds ds in do { in do {es es} } � 1


  2. 10/21/08
  To add error checking � data Exp = Plus Exp Exp  Purely: modify each recursive call to check for and | Minus Exp Exp handle errors. � | Times Exp Exp | Div Exp Exp  Impurely: throw an exception, wrap with a handler. � | Const Int  To add logging � eval :: Exp -> Int  Purely: modify each recursive call to thread a log. � eval (Plus e1 e2) = (eval e1) + (eval e2)  Impurely: write to a file or global variable. � eval (Minus e1 e2) = (eval e1) - (eval e2) eval (Times e1 e2) = (eval e1) * (eval e2)  To add a count of the number of operations � eval (Div e1 e2) = (eval e1) `div` (eval e2) eval (Const i) = i  Purely: modify each recursive call to thread count. �  Impurely: increment a global variable. � answer = eval (Div (Const 3) (Plus (Const 4) (Const 2))) Clearly the imperative approach is easier! �  Modify code to check for division by zero: �  Modify code to check for division by zero: � data Hope a = Ok a | Error String data Hope a = Ok a | Error String eval1 :: Exp -> Hope Int eval1 :: Exp -> Hope Int -- Plus, Minus, Times cases omitted, but similar. -- Plus, Minus, Times cases omitted, but similar. eval1 (Div e1 e2) = eval1 (Div e1 e2) = case eval1 e1 of case eval1 e1 of Ok v1 -> Ok v1 -> case eval1 e2 of case eval1 e2 of Ok v2 -> if v2 == 0 then Error "divby0" Ok v2 -> if v2 == 0 then Error "divby0" else Ok (v1 `div` v2) else Ok (v1 `div` v2) Error s -> Error s Error s -> Error s Error s -> Error s Error s -> Error s eval1 (Const i) = Ok i eval1 (Const i) = Ok i Note: whenever an expression evaluates to Error , that Yuck! � Error propagates to final result. �  We can abstract how Error flows through the code with a higher-order function: �  Compare the types of these functions: � ifOKthen :: Hope a -> (a -> Hope b) -> Hope b e `ifOKthen` k = case e of Ok x -> k x ifOKthen :: Hope a -> (a -> Hope b) -> Hope b Error s -> Error s Ok :: a -> Hope a -- constructor for Hope (>>=) :: IO a -> (a -> IO b) -> IO b eval2 :: Exp -> Hope Int return :: a -> IO a -- Cases for Plus and Minus omitted eval2 (Times e1 e2) =  The similarities are not accidental! � eval2 e1 `ifOKthen` (\v1 -> eval2 e2 `ifOKthen` (\v2 -> Ok(v1 * v2)))  Like IO , Hope is a monad. � eval2 (Div e1 e2) =  IO threads the “world” through functional code. � eval2 e1 `ifOKthen` (\v1 -> eval2 e2 `ifOKthen` (\v2 ->  Hope threads whether an error has occurred. � if v2 == 0 then Error "divby0"  Monads can describe many kinds of plumbing! � else Ok(v1 `div` v2))) eval2 (Const i) = Ok i 2


  3. 10/21/08
 e `ifOKthen` k = case e of Ok x -> k x Error s -> Error s  A monad consists of: � First M Fir t Mona nad L d Law: : return x >>= k = k x  A type constructor M � Ok x `ifOKthen` k  A function return :: a -> M a = case Ok x of Ok x -> k x Error s -> Error s  A function >>= :: M a -> ( a -> M b) -> M b = k x  �  Where >>= and return obey these laws: � Second M Se d Mona nad L d Law: : m >>= return = m m `ifOKthen` Ok (1) return x >>= k = k x = case m of Ok x -> Ok x (2) m >>= return = m Error s -> Error s (3) m1 >>= (\x->m2 >>= \y->m3) = m  � = Thir ird M d Mona nad L d Law w (l (left a t as an e s an exercise cise) � (m1 >>= \x->m2) >>= \y->m3 x not in free vars of m3 m1 >>= (\x->m2 >>= \y->m3) = (m1 >>= \x->m2) >>= \y->m3  A monad consists of: �  We can overload operators to work on many types: �  A type constructor M � (==) :: Int -> Int -> Bool  A function return :: a -> M a (==) :: Char -> Char -> Bool (==) :: [Int]-> [Int]-> Bool  A function >>= :: M a -> ( a -> M b) -> M b  Type classes and instances capture this pattern: � So, there are many different type class Eq a where (constructors) that are monads, (==) :: a -> a -> Bool ... each with these operations.... � instance Eq Int where (==) = primIntEq ...that sounds like a job for a type (constructor) class! � instance Eq a => Eq [a] where (x:xs) == (y:ys) = x==y && xs == ys ...  We can define type classes over type constructors: �  The Haskell Prelude defines a type constructor class for monadic behavior: � class HasMap c where -- HasMap = Functor map :: (a->b) -> c a -> c b class Monad m where instance HasMap [] where return :: a -> m a map f [] = [] (>>=) :: m a -> (a -> m b) -> m b map f (x:xs) = f x : map f xs instance HasMap Tree where  The Prelude defines an instance of this class map f (Leaf x) = Leaf (f x) for the IO type constructor. � map f (Node(t1,t2)) = Node(map f t1, map f t2) instance HasMap Opt where  The “do” notation works over any instance of map f (Some s) = Some (f s) class Monad . � map f None = None We can do the same thing for monads! � 3


Download Presentation
Download Policy: The content available on the website is offered to you 'AS IS' for your personal information and use only. It cannot be commercialized, licensed, or distributed on other websites without prior consent from the author. To download a presentation, simply click this link. If you encounter any difficulties during the download process, it's possible that the publisher has removed the file from their server.

Recommend


More recommend