पाठ 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 greeting

Reach 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.

त्वरित जाँच: 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.