Lesson 11 / 25
Applicative: Combining Independent Effects
Combine several values in a context with <*>, liftA2 and traverse.
Applying functions of several arguments
fmap handles functions of one argument. To combine several values that are each in a context, such as two Maybe values or two IO actions, use Applicative. It adds pure :: a -> f a (put a plain value into the context) and <*> (apply a function in a context to a value in a context). The idiom f <$> x <*> y <*> z applies a three-argument function to three contextual values: Signup <$> validName n <*> validEmail e <*> validAge a. liftA2 f x y is equivalent to f <$> x <*> y. With Maybe, the result is Nothing if any input is Nothing; with lists, <*> produces all combinations; with IO, effects run left to right. The key difference from Monad: in an applicative expression, the effects are independent of each other's results, which allows parallel or analysable effects, and Validation-style types (such as from the validation package) can accumulate all errors instead of stopping at the first. traverse and sequenceA use Applicative to run an effectful function over every element of a structure: traverse readMaybe ["1", "2"] is Just [1, 2].
Applicative style
Combining independent results and traversing lists.
import Text.Read (readMaybe)
import Control.Applicative (liftA2)
data Dimensions = Dimensions { widthCm :: Int, heightCm :: Int, depthCm :: Int }
deriving Show
parseDims :: String -> String -> String -> Maybe Dimensions
parseDims w h d = Dimensions <$> readMaybe w <*> readMaybe h <*> readMaybe d
volumetricWeightGrams :: Dimensions -> Int
volumetricWeightGrams (Dimensions w h d) = (w * h * d) `div` 5 -- (cm^3 / 5000) kg, in grams
main :: IO ()
main = do
print (parseDims "30" "20" "10") -- Just (Dimensions 30 20 10)
print (parseDims "30" "twenty" "10") -- Nothing
print (volumetricWeightGrams <$> parseDims "30" "20" "10") -- Just 1200
print (liftA2 (+) (Just 3) (Just (4 :: Int))) -- Just 7
print ((,) <$> [1, 2 :: Int] <*> "ab") -- [(1,'a'),(1,'b'),(2,'a'),(2,'b')]
print (traverse readMaybe ["1", "2", "3"] :: Maybe [Int]) -- Just [1,2,3]
print (sequenceA [Just 1, Nothing, Just (3 :: Int)]) -- Nothing
greeting <- (++) <$> pure "Hello, " <*> getLine -- effects run left to right
putStrLn greetingReach for Applicative before Monad
If later steps do not depend on earlier results, applicative style (f <$> a <*> b) states that independence clearly and works with more types, including error-accumulating validation.
Quick check: What does `f <$> Just 1 <*> Nothing` evaluate to (for a two-argument f)?
- Just (f 1)
- Nothing
- An exception
- Just 1
Answer
Nothing — If any applicative input is Nothing, the result is Nothing.