高阶函数
假设现在有一个计算销售额的函数,输入年份n,返回该年的销售额。
sales :: num -> num
我们可以这样定义一个计算总销售额的函数
totalSales :: num -> num
totalSales 0 = 0
totalSales n = totalSales (n-1) + sales (n-1)
定义高阶函数
total :: (num -> num) -> num -> num
total f n = total f (n-1) + f (n-1)
得到如下totalSales
totalSales = total sales
如果要定义一个新的销售额计算函数的话,这样就可以
totalNewSales = total newSales
同样的方法,如果要对list中的所有数字取整,可以这么玩
roundList :: [num] -> [num]
roundList [] = []
roundList (x:xs) = round x : roundList xs
round v = entier (v + 0.5)
通过map的思路可以这样定义
mapNum :: (num -> num) -> [num] -> [num]
mapNum f [] = []
mapNum f (x:xs) = f x : mapNum f xs
因此 roundList = mapNum round
以上的mapNum和total通过higher-order实现了reuse。
多态
先来一个计算列表长度的函数
# [] = 0
# (x:xs) = # xs + 1
从定义可以看出函数(#)是类型无关的。
# :: [*] -> num
就是说(#)可以是
# :: [[num]] -> num
# :: [bool] -> num
像(#)这样的函数就是所谓的多态。
有一个特别的函数--也是多态的。[Num]得到整数列表,[String]得到字符串列表。
还有比如swap函数
swap :: (*, **) -> (**, *)
swap (a, b) = (b, a)
可以衍生出
swap :: (bool, num) -> (num, bool)
由此可见多态也是实现reuse的一种方法。
高阶函数和多态的混合使用
前面谈到高阶函数的时候提到了mapNum函数,结合多态可以实现一个general的map函数。
map :: (* -> **) -> [*] -> [**]
map f [] = []
map f (x:xs) = f x: map f xs
类似于map这样的函数,统称为general函数。
从两个维度可以认识map函数的generality:一是f的输入list类型是任意的;二是一旦输入输出类型确定,那么操作f的类型以及整个函数的类型也就确定了
那么问题来了,怎么抽象出一个general函数?
现在有一个求和函数定义如下:
sum :: [num] -> num
sum [] = 0
sum (x:xs) = x + sum xs
首先,我们把(+)函数和初值0抽象出来,可以得到
foldr :: (num -> num -> num) -> num -> [num] -> num
foldr f st [] = st
foldr f st (x:xs) = f x (foldr f st xs)
因此sum = foldr (+) 0
然后,进行多态化
foldr :: (* -> * -> *) -> * -> [*] -> *
通过多态的foldr,可以这么实现concat函数
concat :: [[*]] -> [*]
concat = foldr (++) []
事实上,更加general的foldr可以定义如下:
foldr :: (* -> ** -> **) -> ** -> [*] -> **
也就是说,折叠在foldr中的函数的返回值类型不必和list中的元素类型相同,譬如利用foldr来实现确定list长度的函数(#)现在是这样的:
# = foldr addOne 0
where addOne val len = len + 1
list的本质
结合通过上面的介绍,我们进一步来探讨列表类型list的本质。
list的构造子定义:
[] :: [*]
(:) :: * -> [*] -> [*]
所以[1, 2]等价于1 : (2 : [])
假设现在有个列表操作函数g,定义如下:
g [] = st
g (x:xs) = h (g xs) x xs
那么通过这样的模式,对foldr进一步抽象得到
fold :: (** -> * -> [*] -> **) -> ** -> [*] -> **
fold h st [] = st
fold h st (x:xs) = h (fold h st xs) x xs
通过fold,可以定义诸如tail
tail = fold tailPart (error "tail)
where tailPart v x xs = xs
map g = fold h empty
where h v x xs = cons (g x) v
再进一步,假设有一个类型家族t *(例如tree),提供这样的操作
empty :: t *
cons :: * -> t * -> t *
fold :: (** -> * -> t * -> **) -> ** -> t * -> **
那么理论上就可以对t *类型族的对象实现list操作。
譬如对t *的map操作就可以是这样的
mapGen :: tail (t *,
* -> t * -> t *,
(** -> * -> t * -> **) -> ** -> t * -> **) -> (*** -> ****) -> t *** -> t ****
mapGen (empty, cons, fold) g
= fold h empty
where h v x xs = cons (g x) v
Tree as lists
tree * ::= Leaf | Node * (tree *) (tree *)
相对应的empty/cons/fold定义如下
empty = Leaf
cons a Leaf = Node a Leaf Leaf
cons a (Node b t1 t2) = Node b (cons a t1) t2
cons操作图例:
图一 插入节点 Node b前
图二 插入节点Node b之后
把tree当做list,实现fold操作
fold f st Leaf = st
fold f st t = f (fold f st t1) b t1
where (b, t1) = splitTree t
定义splitTree如下
splitTree :: tree * -> (*, tree *)
splitTree Leaf = error "splitTree"
splitTree (Node a Leaf t) = (a, t)
splitTree (Node a t1 t2) = (b, Node a t11 t2)
where (b, t11) = splitTree t1
Tree-like types
一种典型的针对tree的操作形如:
h Leaf = st
h (Node a t1 t2) = ...h t1...h t2...a...t1...t2...
定义h = (treeRec f st)得到treeRec:
treeRec :: (** -> ** -> * -> tree * -> tree * -> **) ->
** -> tree * -> **
treeRec f st Leaf = st
treeRec f st (Node a t1 t2) = f (treeRec f st t1) (treeRec f st t2) a t1 t2
用(t *)替代(tree *),定义操作:
leaf :: t *
node :: * -> t * -> t * -> t *
treeRec :: (** -> ** -> * -> t * -> t * -> **) -> ** -> t* -> **
通过这几个接口定义tree sort:
tSort :: t * -> [*]
tSort = treeRec mergeVal []
where mergeVal sort1 sort2 val t1 t2 = mVal sort1 sort2 val
mVal实现了两个sorted list排序并且插入val节点。这是其中一种实现:
mVal :: [*] -> [*] -> * -> [*]
mVal sort1 [] val = insertSort val sort1
mVal [] sort2 val = insertSort val sort2
mval sort1 sort2 val = insertSort val (mergeSort sort1 sort2)
where mergeSort [] sort2 = sort2
mergeSort (x:xs) sort2 = mergeSort xs (insertSort x sort2)
insertSort val [] = [val]
insertSort val (x:xs)@list = if x <= val
then x:insertSort (val, xs)
else val:list -- inc sort
递归访问一棵树的过程本质上用的是分制(divide and conquer)的思想,因此把任意的数据类型当做tree其实原理都是一样的。
Lists as trees
把list定义成tree
leaf = []
node a l1 l2 = t1 ++ [a] ++ t2
得到
treeRec f st [] = st
treeRec f st l = f v1 v2 a t1 t2
where v1 = treeRec f st t1
v2 = treeRec f st t2
(a, t1, t2) = listToTree l
listToTree :: [*] -> (*, [*], [*])
listToTree [] = error "listToTree"
listToTree (a:x) = (a, l1, l2)
where n = #x div 2
l1 = take n x
l2 = drop n x
这里的listToTree通过不断的二分制作子树,并且把每次二分前的list的第一个元素当做新的子树的根节点。
另一种listToTree定义:
listToTree (x:xs) = (a, [b | b<-x, b <= a], [b | b<-x, b > a])
利用第二种listToTree定义,定义flatten函数:
flatten = treeRec joinUp []
where joinUp flat1 flat2 a t1 t2 = flat1 ++ [a] ++ flat2
此时flatten函数等价于quicksort。
总结
文章展示了通过高阶函数和多态,实现一个list,在一个scope下即可以当列表实现不同的列表操作,也可以当做tree实现各种树的操作。
软件的重用完全体现在了类型的定义上,而定义类型的过程事实上就是设计算法的过程,这也是所谓的'algorithm oriented' stype。
原文:Simon Thompson. Higher-order + Polymorphic = Reusable Computing Laboratory. University of Kent Canterbury, CT2 7NF, U.K.