复用 = 高阶函数和多态

高阶函数

假设现在有一个计算销售额的函数,输入年份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.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叶玄青

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值