20-CS-4003-001 |
Organization of Programming Languages |
Fall 2018 |
---|---|---|

Functors |

Prev
Next
All lectures
Code

**Applicative Functors**

import Data.Map as M import Data.Maybe import Control.Monad import Data.List {- So far, when we were mapping functions over functors, we usually mapped functions that take only one parameter. But what happens when we map a function like *, which takes two parameters, over a functor? Functions below are mapped inside of Maybe or [] (another Functor) -} x1 = fmap (*) (Just 3) {- x1 :: Maybe (Integer -> Integer) -} x2 = fmap (++) (Just "Hello") {- x2 :: Maybe ([Char] -> [Char]) -} x3 = fmap (\x y z -> (x+y)/z) [3,4,5,6] {- x3 :: [Double -> Double -> Double] -} {- f we map compare, which has a type of (Ord a) => a -> a -> Ordering over a list of characters, we get a list of functions of type Char -> Ordering, because the function compare gets partially applied with the characters in the list. It's not a list of (Ord a) => a -> Ordering function, because the first a that got applied was a Char and so the second a has to decide to be of type Char. -} x4 = fmap compare "Hello" {- x4 :: [Char -> Ordering] -} x5 = fmap (\f -> (f 4)) x1 x6 = fmap (\f -> (f " World")) x2 x7 = fmap (\f -> (f 2 3)) x3 x8 = fmap (\f -> (f 'e')) x4 {- But what if we have a functor value of Just (3 *) and a functor value of Just 5 and we want to take out the function from Just (3 *) and map it over Just 5? With normal functors, we're out of luck, because all they support is just mapping normal functions over existing functors. x5 = fmap (\f -> (f (Just 5))) x1 -} class (Functor f) => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b {- if we want to make a type constructor part of the Applicative typeclass, it has to be in Functor first. Thus we can use fmap pure puts a value in a default context <*> from within the functor context f, applies a function (a -> b) to argument of type a to get an object of type b wrapped inside the functor context f. <*> changes a function inside the functor to a function over values of the functor. This is just syntactic sugar for covering the use of fmap In the following use Maybe instead of Maybe a since Applicative is a type over a concrete type parameter -} instance Applicative Maybe where pure = Just Nothing <*> _ = Nothing (Just f) <*> something = fmap f something {- examples -} x9 = (Just (*3)) <*> (Just 4) {- no can do: x9 = fmap (Just (*3)) (Just 3) -} x10 = (pure (*3)) <*> (Just 4) x11 = (Just (*3)) <*> (pure 4) x12 = (pure (++ " World")) <*> (Just "Hello") {- what does work: fmap (*3) (Just 3) fmap (++ " World") (Just "Hello") -} {- f1 is a function of four arguments not in a context, can be applied to objects also not in a context using the applicative functor <*> -} f1 = (\x y z w -> (x+y+z+w)) x13 = (pure f1) <*> (Just 3) <*> (Just 4) <*> (Just 5) <*> (Just 6) {- Note: pure f <*> x <*> y <*> z same as fmap f x <*> y <*> z -} {- More syntactic sugar - use a normal function on Applicative functors class (Functor f) => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b (<$>) :: (Functor f) => (a -> b) -> f a -> f b -} (<$>) :: (Functor f) => (a -> b) -> f a -> f b f <$> x = fmap f x x14 = (++) <$> Just "Hello" <*> Just " World" {- List constructors are Applicative functors -} instance Applicative [] where pure x = [x] fs <*> xs = [f x | f <- fs, x <- xs] x15 = pure 1::[Int] x16 = pure "Hello"::[[Char]] x17 = pure "Hello"::Maybe [Char] x18 = [(*0),(+100),(^2)] <*> [1,2,3] {- cross product? -} {- [(+),(*)] <*> [1,2] -> [(1+),(2+),(1*),(2*)] comp first in the following -} x19 = [(+),(*)] <*> [1,2] <*> [3,4] {- [4,5,5,6,3,4,6,8] -} {- same as -} x20 = [ f x y | f <- [(+),(*)], x <- [1,2], y <- [3,4]] {- do syntax is about taking several I/O actions and gluing them into one, which is exactly what we do here. -} instance Applicative IO where pure = return a <*> b = do f <- a x <- b return (f x) {- The following takes two inputs and glues them together -} inpAct1 :: IO String inpAct1 = do a <- getLine b <- getLine return $ a ++ b {- Alternative expression of the above -} inpAct2 :: IO String inpAct2 = (++) <$> getLine <*> getLine inpAct3 = do a <- getLine {- a is a String -} b <- getLine {- b is a String -} let c = a ++ b {- c is a String -} putStrLn $ "Result: " ++ c inpAct3a = do a <- getLine {- a is a String -} b <- getLine {- b is a String -} putStrLn $ "Result: " ++ a ++ b inpAct4 = do a <- (++) <$> getLine <*> getLine {- a is a String -} putStrLn $ "Result: " ++ a liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c liftA2 f a b = f <$> a <*> b x21 = liftA2 (++) ["Hello"] ["There"] x22 = liftA2 (:) (Just 3) (Just [4]) sequenceA :: (Applicative f) => [f a] -> f [a] sequenceA [] = pure [] sequenceA (x:xs) = (:) <$> x <*> sequenceA xs x23 = sequenceA [(Just 1),(Just 2),(Just 3)] x24 = sequenceA [getLine, getLine, getLine] {- Applicative functors allow combining different computations, such as I/O computations, non-deterministic computations, computations that might have failed, etc. by using the applicative style. Just by using <$> and <*> we can use normal functions to uniformly operate on any number of applicative functors and take advantage of the semantics of each one. -} {- The following is taken from |
||