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"]