Haskell 笔记:Applicative
原文链接 https://hsfzxjy.github.io/2018-11-18-haskell-applicative/
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。
Motivation
Functor
solves the problem of mapping regular one-parameter functions into a sub-category, but that's not easy for functions with more than one parameters.
Let's consider a function with two parameters f :: a -> b -> c
, which can also read as a -> (b -> c)
. Applying fmap
on f
, we will get fmap f :: m a -> m (b -> c)
. There's still some distance from what we want: f' :: m a -> m b -> m c
. To get f'
, we need a transform from m (b -> c)
to m b -> m c
. Here we denote it as <*> :: m (b -> c) -> m b -> m c
. We will later show that such transform is universal for functions with more parameters.
Now consider a function with three parameters f :: a -> b -> c -> d
. We are going to transform it into a wrapped-value version, with the help of fmap
and <*>
.
f :: a -> b -> c -> d
(fmap f) :: m a -> m (b -> (c -> d))
\a_ b_ -> (fmap f a_) <*> b_
:: m a -> m b -> m (c -> d)
\a_ b_ c_ -> ((fmap f a_) <*> b_) <*> c_
:: m a -> m b -> m c -> (m d)
Here \a_ b_ c_ -> ((fmap f a_) <*> b_) <*> c_
is in the desired type. For most of the time, applying parameters directly is actually what we want, instead of the function itself, so the code could simply be written as ((fmap f a) <*> b) <*> c
, where a
, b
and c
are wrapped values. Parenthesis could be omitted if precedences are set properly, which leads to a neat and easy-to-read form:
f `fmap` a <*> b <*> c
In haskell, fmap
has an infix name <$>
. So finally we get: f <$> a <*> b <*> c
.
Applicative
Haskell pre-defined a type class Applicative
, which captures the pattern <*>
. Any type that implements Applicative
works well with <$>
and <*>
.
class Functor f => Applicative (f :: * -> *) where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
GHC.Base.liftA2 :: (a -> b -> c) -> f a -> f b -> f c
(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a
Note that an Applicative
is also a Functor
. Apart from <*>
, there are some other helper functions or operators in Applicative
.
pure
is equivalent to the default value constructor of f
, e.g. (:[])
for List
or Just
for Maybe
. This may be handful when lifting an unwrapped value to a wrapped one.
liftA2
transforms a binary operator to the corresponding version. The function exists as binary operators would be frequently passed among high-order functions.
*>
takes two wrapped parameters and simply returns the second one, which sequence up two wrapped values. This is quite useful for Applicative
with action semantics, such as IO
. In fact, it's so useful that Haskell introduces a syntax sugar for it, known as the do-notation
. Particularly:
do
putStrLn "1"
putStrLn "2"
is equivalent to
putStrLn "1" *> putStrLn "2"
<*
is similar. Both will be reviewed while studying Monad.