作为一种函数式语言,haskell提供了各种高级的函数编程抽象支持:Functor抽象了那些作用于函数(或者类型封装)内的数据的操作并且将其运算结果用对应函数封装的抽象运算 , 其核心是提供了Functor typeclass 和 fmap 操作。
一些基本抽象函数
在传统的函数式语言中,map/filter/any 通常是最为builtin的高阶函数提供的,它们都携带一个函数作为第一个参数,并且作用于一个list作为第二个参数,并且返回满足条件的结果 – 或者是一个子list,或者是一个Bool,如下:
1
2
3
4
5
6
7
8
9
10
Prelude > : type map
map :: ( a -> b ) -> [ a ] -> [ b ]
Prelude > : type fmap
fmap :: Functor f => ( a -> b ) -> f a -> f b
Prelude > : type filter
filter :: ( a -> Bool ) -> [ a ] -> [ a ]
Prelude > : type any
any :: ( a -> Bool ) -> [ a ] -> Bool
Prelude > : type all
all :: ( a -> Bool ) -> [ a ] -> Bool
这些函数的意义通常都比较简单明了,不过它们仅仅作用于list类型。对于更复杂的类型,譬如说一个wrapper类型,这些函数就无能为力了。
更高级的抽象 – Functor
考虑如下的例子,定义一个Tree类型:
1
2
3
Tree a = Node ( Tree a ) ( Tree a )
| Leaf a
deriving ( Show )
这里的Tree中保存的是一个抽象数据。假设这里的a是个String,并且我们希望据此生成一个新的Tree,对应的每个节点中的数据存放的是对应String的长度,那么其实现可以如下:
1
2
treeLengths ( Leaf s ) = Leaf ( length s )
treeLengths ( Node l r ) = Node ( treeLengths l ) ( treeLengths r )
因为这里的length仅仅是一个作用于所封装的类型(String)的一个函数,一个很自然的想法是可以将这个函数操作本身抽象出来,生成一个抽象的TreeMap:
1
2
3
treeMap :: ( a -> b ) -> Tree a -> Tree b
treeMap f ( Leaf a ) = Leaf ( f a )
treeMap f ( Node l r ) = Node ( treeMap l ) ( treeMap r )
这里通过定义treeMap,实现将参数函数应用于ADT类型Tree中所封装的类型,将函数运算结果重新用Tree做封装。Functor就是绑定于某一个函数(譬如这里的Tree类型构造)之上并定义了一个fmap 函数的typeclass:
1
2
class Functor f where
fmap :: ( a -> b ) -> f a -> f b
通过上述定义,我们可以发现treeMap实际完成了famp的功能;自然地可以让Tree作为Functor的一个instance来继承这个Typeclass:
1
2
instance Functor Tree where
fmap = treeMap
list & Maybe
Maybe类型是一个基本类型,用于封装某个数据或者空,而List则用于描述数据列表。对于List类型,其fmap对应的实现其实就是map – fmap可以看作是map的一个扩展 ; 对于Maybe类型,fmap的定义如下:
1
2
3
instance Functor Maybe where
fmap _ Nothing = Nothing
fmap f ( Just x ) = Just ( f x )
也就是说,对于空数据,famp的结果仍然是空,而对于实际封装的数据,则返回Just封装的函数运算结果。
Monad, liftM 和 ap
Monad 是一种特殊的typeclass,其封装的原始数据在任何运算过程中都不能暴露其中间运算结果,任何一个monadic函数返回的都是一个新的monadic变量;如果需要将一个纯函数作用于Monad所封装的数据类型,并得到一个对于元算结果的Monad封装,则需要用liftM :
1
2
liftM :: ( Monad m ) => ( a -> b ) -> m a -> m b
liftM f m = m >>= \ i -> return ( f i )
对于多个变量的函数,haskell中定义了liftM2/liftM3…liftM5,以下是liftM2的定义:
1
2
liftM2 :: ( Monad m ) => ( a -> b -> c ) -> m a -> m b -> m c
liftM2 f m1 m2 = m1 >>= \ a -> m2 >>= \ b -> return ( f a b )
这里的操作可以依次作用于2个monadic变量,并且得到一个新的moandic变量。对于无穷集合运算来说,liftM系列函数就无能无力了;这个时候 ap则可以派上用场:
1
ap :: Monad m => m ( a -> b ) -> m a -> m b
如下例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
data MovieReview = MovieReview {
revTitle :: String
, revUser :: String
, revReview :: String
}
lookup1 key alist = case lookup key alist of
Just ( Just s @ ( _ : _ )) -> Just s
_ -> Nothing
apReview :: [( String , Maybe String )] -> Maybe MovieReview
apReview alist = MovieReview ` liftM ` lookup1 "title" alist
` ap ` lookup1 "user" alist
` ap ` lookup1 "review" alist
-- identical impl by liftM3
liftedReview alist =
liftM3 MovieReview ( lookup1 "title" alist )
( lookup1 "user" alist )
( lookup1 "review" alist )
这里有2中等价的实现方式,而liftM结合ap的方式提供了更高的灵活性,可以直接扩展至多个参数的情形。
参考