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 getChars with sequenceA:
In [ ]:
getChars :: Int -> IO String
getChars n = sequenceA (replicate n getChar)

Applicative Laws¶

  1. Identity: pure id <*> x = x
  2. Homomorphism: pure (g x) = pure g <*> pure x
  3. Interchange: x <*> pure y = pure (\g -> g y) <*> x
  4. Composition: x <*> (y <*> z) = (pure (.) <*> x <*> y) <*> z