Applicative
Prerequisites: Functor and Foldable
fmap applies a one-argument function inside a container. What about a
function with two arguments, where both values are wrapped?
main :: IO ()
main = do
print ((+) <$> Just 3 <*> Just 4)
print ((+) <$> Just 3 <*> Nothing)
print (max <$> Right 10 <*> (Left "oops" :: Either String Int))
print (pure 5 :: Maybe Int)
print ((,) <$> Just 'a' <*> Just True)
Just 7
Nothing
Left "oops"
Just 5
Just ('a',True)
How it works:
(+) <$> Just 3isJust (+ 3)— a function inside aMaybe.<*>("apply") applies a wrapped function to a wrapped value.- If anything is
Nothing(orLeft), the whole result is.
-- illustrative: the class definition
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
pure puts a plain value into the container with no extra effect.
Building records from validated parts
The pattern Constructor <$> a <*> b <*> c is the everyday use of
Applicative:
import Text.Read (readMaybe)
data Person = Person {name :: String, age :: Int}
deriving (Show)
validName :: String -> Maybe String
validName "" = Nothing
validName n = Just n
validAge :: String -> Maybe Int
validAge s = case readMaybe s of
Just a | a >= 0 && a < 150 -> Just a
_ -> Nothing
mkPerson :: String -> String -> Maybe Person
mkPerson n a = Person <$> validName n <*> validAge a
main :: IO ()
main = do
print (mkPerson "Ada" "36")
print (mkPerson "" "36")
print (mkPerson "Ada" "-1")
Just (Person {name = "Ada", age = 36})
Nothing
Nothing
Lists as Applicative
For lists, <*> combines every function with every value —
like a nested loop or list comprehension:
main :: IO ()
main = do
print ((+) <$> [1, 2] <*> [10, 20, 30])
print ((,) <$> "ab" <*> [True, False])
print (zipWith3 (,,) [1, 2] "xy" [True, False])
[11,21,31,12,22,32]
[('a',True),('a',False),('b',True),('b',False)]
[(1,'x',True),(2,'y',False)]
IO as Applicative
IO is Applicative too, so you can combine the results of actions
without naming them:
-- Try it: printf 'Ada\nLovelace\n' | runghc main.hs
main :: IO ()
main = do
fullName <- (\a b -> a ++ " " ++ b) <$> getLine <*> getLine
putStrLn ("Hello, " ++ fullName)
Hello, Ada Lovelace
*> runs two actions and keeps the second result, <* keeps the first.
Applicative is less powerful than Monad (next lesson): each step can't
depend on the result of the previous one — which is exactly what makes
it useful for validation, parsing, and running things in parallel.