Applicatives¶
- Functors allowed only 1-ary functions to be mapped
- Applicatives generalize functors to allow n-ary input functions
- Use case: "applicative" style for effectful programming
In [ ]:
fmap0 :: a -> f a
fmap1 :: (a -> b) -> f a -> f b
fmap2 :: (a -> b -> c) -> f a -> f b -> f c
fmap3 :: (a -> b -> c -> d) -> f a -> f b -> f c -> f d
...
- We could define different types of "functors" for each, but that would not be elegant!
- Instead, we can define this family of "fmaps" using the following two basic functions:
In [ ]:
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
The "pure" function simply converts a value of type a into a structure of type f a
The <*> operator is a generalization of the function application in which the function, it's arguments, and the output are all enclosed with structure f
In [ ]:
fmap0 :: a -> f a
fmap0 x = pure x
fmap1 :: (a -> b) -> f a -> f b
fmap1 g x = pure g <*> x
fmap2 :: (a -> b -> c) -> f a -> f b -> f c
fmap2 g x y = pure g <*> x <*> y
fmap3 :: (a -> b -> c -> d) -> f a -> f b -> f c -> f d
fmap3 g x y z = pure g <*> x <*> y <*> z
...
In [ ]:
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Applicative Instances¶
1. Maybe (Exceptional Computations)¶
In [ ]:
instance Applicative Maybe where
-- pure :: a -> Maybe a
pure = Just
-- (<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
Nothing <*> _ = Nothing
(Just g) <*> mx = fmap g mx
In [1]:
pure (+1) <*> Just 1
Use <$>
Found:
pure (+ 1) <*> Just 1
Why Not:
(+ 1) <$> Just 1
Just 2
In [2]:
pure (+) <*> Just 1 <*> Just 2
Use <$>
Found:
pure (+) <*> Just 1
Why Not:
((+) <$> Just 1)
Just 3
In [3]:
pure (+) <*> Nothing <*> Just 2
Use <$>
Found:
pure (+) <*> Nothing
Why Not:
((+) <$> Nothing)
Nothing
2. Lists (Non-Deterministic Computations)¶
In [4]:
instance Applicative [] where
-- pure :: a -> [a]
pure x = [x]
-- (<*>) :: [a -> b] -> [a] -> [b]
gs <*> xs = [g x | g <- gs, x <- xs]
<interactive>:1:10: error: [GHC-59692]
Duplicate instance declarations:
instance Applicative [] -- Defined at <interactive>:1:10
instance Applicative [] -- Defined in ‘GHC.Base’
In [5]:
pure (*) <*> [1, 2] <*> [3, 4]
Use <$>
Found:
pure (*) <*> [1, 2]
Why Not:
((*) <$> [1, 2])
[3,4,6,8]
In [6]:
pure (*) <*> [1, 2] <*> [3, 4]
Use <$>
Found:
pure (*) <*> [1, 2]
Why Not:
((*) <$> [1, 2])
[3,4,6,8]
List Product Comparison:
In [ ]:
-- List comprehension style:
prods :: [Int] -> [Int] -> [Int]
prods xs ys = [x * y | x <- xs, y <- ys]
-- Applicative style:
prods :: [Int] -> [Int] -> [Int]
prods xs ys = pure (*) <*> xs <*> ys
3. IO (Interactive Computations)¶
In [7]:
instance Applicative IO where
-- pure :: a -> IO a
pure = return
-- (<*>) :: IO (a -> b) -> IO a -> IO b
mg <*> mx = do { g <- mg; x <- mx; return (g x) }
Use <$>
Found:
do g <- mg
x <- mx
return (g x)
Why Not:
do g <- mg
g <$> mx
<interactive>:1:10: error: [GHC-59692]
Duplicate instance declarations:
instance Applicative IO -- Defined at <interactive>:1:10
instance Applicative IO -- Defined in ‘GHC.Base’
In [6]:
getChars1 :: Int -> IO String
getChars1 0 = return []
getChars1 n = pure (:) <*> getChar <*> getChars1 (n - 1)
Use <$>
Found:
pure (:) <*> getChar
Why Not:
((:) <$> getChar)
Generic Applicative Helpers (sequenceA)¶
Besides viewing applicatives as providing support for n-ary functions, there is another abstraction view: we can write expressions in an "applicative" style.
Arguments to functions are no longer plain values but correspond to "effects" (possibility of failure - Maybe, many ways to succeed - List, or performing input/output - IO)
In [7]:
sequenceA :: Applicative f => [f a] -> f [a]
sequenceA [] = pure []
sequenceA (x:xs) = pure (:) <*> x <*> sequenceA xs
Use foldr
Found:
sequenceA [] = pure []
sequenceA (x : xs) = pure (:) <*> x <*> sequenceA xs
Why Not:
sequenceA xs = foldr (\ x -> (<*>) (pure (:) <*> x)) (pure []) xs
Use <$>
Found:
pure (:) <*> x
Why Not:
((:) <$> x)
- Re-implementing
getCharswithsequenceA:
In [ ]:
getChars :: Int -> IO String
getChars n = sequenceA (replicate n getChar)
Applicative Laws¶
- Identity:
pure id <*> x = x - Homomorphism:
pure (g x) = pure g <*> pure x - Interchange:
x <*> pure y = pure (\g -> g y) <*> x - Composition:
x <*> (y <*> z) = (pure (.) <*> x <*> y) <*> z