Applicative
Laws: identity (pure id <*> v = v), composition, homomorphism (pure f <*> pure x = pure (f x)), interchange.
Applicative vs Monad: Applicative effects are independent: the structure of the computation is fixed up front, which enables static analysis and parallelism (e.g., Haxl, optparse-applicative, validation that collects all errors).
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
-- liftA2 :: (a -> b -> c) -> f a -> f b -> f c; (*>) and (<*) sequence, discarding one result
-- Apply a function inside a context to values inside contexts
pure (+) <*> Just 3 <*> Just 4 -- Just 7
(+) <$> Just 3 <*> Just 4 -- Just 7 (the idiomatic style)
(+) <$> Just 3 <*> Nothing -- Nothing
(,) <$> Right 1 <*> Left "error" -- Left "error"
(+) <$> [1, 2] <*> [10, 20] -- [11,21,12,22] (all combinations)
liftA2 max (Just 3) (Just 7) -- Just 7
Just 1 *> Just 2 -- Just 2
Just 1 <* Just 2 -- Just 1
(+) <$> (* 2) <*> (+ 10) $ 3 -- 19: (3*2) + (3+10)
-- Combining independent validations
data Config = Config {host :: String, port :: Int} deriving Show
parseConfig :: [(String, String)] -> Maybe Config
parseConfig env = Config <$> lookup "HOST" env <*> (lookup "PORT" env >>= readMaybe)
-- ZipList: a different Applicative for lists
import Control.Applicative (ZipList (..))
getZipList ((+) <$> ZipList [1, 2, 3] <*> ZipList [10, 20, 30]) -- [11,22,33]
-- Sequencing a lis
…18.1 Validation: Accumulating Errors
-- Either stops at the first error; a Validation-style Applicative accumulates them
data Validation e a = Failure e | Success a deriving Show
instance Functor (Validation e) where
fmap _ (Failure e) = Failure e
fmap f (Success a) = Success (f a)
instance Semigroup e => Applicative (Validation e) where
pure = Success
Failure e1 <*> Failure e2 = Failure (e1 <> e2)
Failure e1 <*> Success _ = Failure e1
Success _ <*> Failure e2 = Failure e2
Success f <*> Success a = Success (f a)
validateUser :: String -> Int -> Validation [String] (String, Int)
validateUser n a =
(,) <$> (if null n then Failure ["empty name"] else Success n)
<*> (if a < 0 then Failure ["negative age"] else Success a)
-- validateUser "" (-1) == Failure ["empty name","negative age"]