简体   繁体   中英

What is the purpose of an applicative functor?

Can anyone share a good real life situation when a function with the following signature would be useful?

f (a -> b) -> f a -> f b

I can't really see where I would need something like the schoolbook examples from learn-you-a-haskell [(+),(*)] <*> [1,2] <*> [3,4]

Applicatives can be used for lots of stuff that you might also do with a monad, but that don't really need the stronger features of that class. (More precisely, whenever the “shape” of the functor doesn't depend on values within , then Applicative is sufficient.) For instance, an action like

foobar :: IO [String]
foobar = do
   fooTxt <- readFile "foo.txt"
   barTxt <- readFile "bar.txt"
   return $ zip (lines fooTxt) (lines barTxt)

could as well be written

foobar = zip <$> (lines <$> readFile "foo.txt")
             <*> (lines <$> readFile "bar.txt")

In this case, that makes it merely a tad shorter, but in other cases it may also improve performance (because Applicative is less general, more optimisations are possible) or allow you to make code more generic than when you use the Monad interface.

The most common use I have of it is to use multiple Monadic (or in this case Applicative) values for a function. A really common way I use it is for constructors.

Consider:

randomAge :: Rand StdGen Int
randomHeight :: Rand StdGen Double
randomWeight :: Rand StdGen Double

data Person = Person { age :: Int, height :: Double, weight :: Double }

randomPerson :: Rand StdGen Person
randomPerson = Person <$> randomAge <*> randomHeight <*> randomWeight

-- If we only had Monads...
randomPerson' :: Rand StdGen Person
randomPerson' = liftM3 Person randomAge randomHeight randomWeight
-- or worse...
randomPerson'' = randomAge >>= \ra -> randomHeight >>= \rh -> randomWeight >>= \rw -> return $ Person ra rh rw

Or this example from FPComplete's tutorial on the JSON parser Aeson :

instance FromJSON Person where
 parseJSON (Object v) =
    Person <$> v .: "firstName"
           <*> v .: "lastName"
           <*> v .: "age"
           <*> v .: "likesPizza"
 parseJSON _ = mzero

Or this example from the Real World Haskell chapter on Parser Combinators :

ghci> let parser = (++) <$> string "HT" <*> (string "TP" <|> string "ML")

For the most part Applicatives are useful when you have f :: a -> b -> c and you have ma and mb and you want an mc .

Edit: That said there are other uses. For example, Applicative can be used as a really good way to modify traversable data structures. Eg applying a tree to a value, by having a tree of functions, thereby turning a value into a tree. Or doing non-deterministic list operations without list comprehension syntax.

It can have some unexpected applications. For example, using the -> Monad:

> (<*>) (+) (+1) 2
5

That last one was a bit more esoteric and isn't likely to be used in a practical circumstance but it shows that you can use Applicatives in many ways.

It's the basic building block (along with pure ) for other very useful functions like liftA2 (which you could think of as " fmap over two containers" and not be too far off the mark):

liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
liftA2 f fa fb = pure f <*> fa <*> fb

So suppose you want to add all combinations of values of two lists xs, ys :: [Integer] , you could do it this way:

liftA2 (+) xs ys

Though more commonly we write this:

(+) <$> xs <*> ys

You are right to be puzzled why would somebody ever write [(+),(*)] <*> [1,2] <*> [3,4] —I think it's a very artificial example. 99% of the time, Applicative is used, you fundamentally have one function that takes two or more arguments, and are looking to apply it to the values inside a functor that implements Applicative .

One way of looking at it is that we could alternatively define Applicative and associated functions like this:

-- Not the real definition
class Functor f => Applicative f where
    pure :: a -> f a

    -- Like `fmap` but for two-argument functions:
    liftA2 :: (a -> b -> c) -> f a -> f b -> f c

-- `<*>` and `liftA2` are interdefinable, as shown further up and just here:
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
ff <*> fa = liftA2 ($) ff fa

But the reason <*> is picked as the class method is that it makes it very easy to write liftA2 , liftA3 , etc.:

liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
liftA2 f fa fb = pure f <*> fa <*> fb

liftA3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d
liftA3 f fa fb fc = pure f <*> fa <*> fb <*> fc

liftA4 :: Applicative f => (a -> b -> c -> d -> e) -> f a -> f b -> f c -> f d -> f e
liftA4 f fa fb fc fd = pure f <*> fa <*> fb <*> fc <*> fd

The Applicative class is very nearly the minimum functionality needed to support this "map with a function of multiple arguments" pattern, which is a useful thing in tons of cases.

As leftaroundabout mentioned, whenever the "shape" of each "action" doesn't depend on the values "produced" by the previous one, Applicative is sufficient.

This may be a shape in a very literal way. For example, one context for >>= is that t >>= f takes values stored in the leaves of a tree t , applies f to each value to produce a new subtree, and grafts each subtree onto the tree in place of its corresponding leaf. This operation, however, doesn't work for a balanced tree, because there's no telling how large/deep a tree f will produce each time. However, <*> may still make sense, because the grafted trees will all have the same shape, maintaining balance.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM