函数作为函子实例的理解
函数作为函子实例的理解
((->) r),或者说函数,也是函子 (Functor)
的实例,这个概念理解起来通常有一点困难,让我们通过一个例题来体验一下。
题目是判断一个给定的字符串是否为回文串。思路很简单,对于给定的字符串
str,只需要判断其反转后的结果是否与原串相同即可。
Naive
最普通的写法如下: 1
2palindrome :: String -> Bool
palindrome str = str == reverse str
Applicative
更进一步,考虑 ((->) r) 是 Applicative 函子的实例,有
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21λ> :info Applicative
type Applicative :: (* -> *) -> Constraint
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
{-# MINIMAL pure, ((<*>) | liftA2) #-}
-- Defined in ‘GHC.Base’
instance Applicative (Either e) -- Defined in ‘Data.Either’
instance Applicative [] -- Defined in ‘GHC.Base’
instance Applicative Maybe -- Defined in ‘GHC.Base’
instance Applicative IO -- Defined in ‘GHC.Base’
instance Applicative ((->) r) -- Defined in ‘GHC.Base’ -- 在这里
instance (Monoid a, Monoid b, Monoid c) =>
Applicative ((,,,) a b c)
-- Defined in ‘GHC.Base’
instance (Monoid a, Monoid b) => Applicative ((,,) a b)
-- Defined in ‘GHC.Base’
instance Monoid a => Applicative ((,) a) -- Defined in ‘GHC.Base’
((->) r) 作为 Applicative 的实例实现如下
1 | instance Applicative ((->) r) where |
这里一定要弄清楚,pure 的签名是
pure :: a -> (r -> a),因为 ((->) r)
本身是函子,将 a 类型的东西应用于这个函子仍应该返回
a 类型本身。
f 的类型是 f :: r -> (a -> b),即
a -> b 在 ((->) r)
的上下文中,类似地,有 g :: r -> a,根据
<*> 的定义,我们需要返回
r -> b,即上述的实现(x 为 r
类型)。
此时考虑有 ((->) String) 的函子,对其应用
(==) :: String -> String -> Bool 和
reverse :: String -> String,得到
1 | palindrome :: String -> Bool |
这里的 (==) :: String -> String -> Bool 处在
((->) String) 的上下文中,构成了新的类型
String -> String -> String -> Bool。让我们根据
<*> 的定义将其展开,有
1 | (==) <*> reverse |
以上就是回文数判断的另一种写法。
在研究 Applicative 的时候一定不要忘了
liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c,对于我们的题目来说,它的签名应该是
liftA2 :: (String -> String -> Bool) -> (String -> String) -> (String -> String) -> (String -> Bool),即
1 | palindrome = liftA2 (==) id reverse -- 本题 id 和 reverse 的顺序可以互换 |
Functor
当然也可以用 Funtor 去考虑,((->) r) 也是 Funtor
的实例。Functor 由于不带上下文,所以我们要手工添加一些上下文
1 | palindrome str = (== str) <$> reverse |
此处
(== str) :: String -> Bool,(<$>) :: (String -> Bool) -> (String -> String) -> (String -> Bool),值得注意的是
<$> 的两个参数中,第一个的 String
是函数本身的 String,而第二个参数
(String -> String) 的参数才是 ((->) r)
中的 r。
Monad
没错,((->) r) 也是 Monad 的实例,参考之前的想法,将
((->) String) 看做 Monad m 的
m,对于
(>>=) :: (Monad m) => m a -> (a -> m b) -> m b,第一个参数应该是一个
r -> String 类型的东西,而第二个参数应该是
String -> (String -> Bool),有 1
palindrome = reverse >>= (==)