让我们使用一些等式伪代码,为了清楚起见省略了一些括号(因此,我们f x y
为 call编写(f x y)
,这是明确的):
multirember&Co a lat col
= col [] [] , IF lat == []
= multirember&Co a (cdr lat)
( newlat seen =>
col newlat
(cons (car lat) seen) ) , IF (car lat) == a
= multirember&Co a (cdr lat)
( newlat seen =>
col (cons (car lat) newlat)
seen ) , OTHERWISE
不言自明,这是做什么的?:) 还没有?:) 用想象中的模式匹配伪代码(带警卫)再次重写,我们有
multirember&Co = g where
g a [b, ...lat] col | b == a = g a lat ( n s => col n [b, ...s] )
| else = g a lat ( n s => col [b, ...n] s )
g a [] col = col [] []
模式匹配的语义应该很明显:[b, ...lat]
匹配[1,2,3]
whereb = 1
和lat = [2,3]
. 因此,这只是一个三种情况的方程:
当第二个参数是一个空列表时,“收集器”函数col
会立即输入两个空列表作为它的两个参数;
当第二个参数(它是一个列表)的头部元素与第一个参数相同时,结果与使用列表尾部递归的结果相同,修改后的收集器 --之后将接收其两个参数,n
和s
, --将当前头元素(即a
)添加到s
列表中,并将两个列表提供给此调用的收集器函数col
;
否则,头元素将被添加到n
列表中,之后被构造的收集器接收,n
并且s
两者都将被进一步馈送到电流收集器函数中。
换句话说,我们正在处理从递归调用返回的两个结果,如果 head 是a
,则将 head 添加到第二个结果,如果不是,则将其添加到第一个。
因此调用
(g 1 [ 2, 1, 3, 1, 4, 5 ] col)
与(将导致)调用相同
(col [ 2, ...[3, ...[4, ...[5, ...[]]]]]
[ 1, ...[1, ...[]] ])
IE
(col [ 2, 3, 4, 5 ]
[ 1, 1 ])
另一种看待它的方式是,以下是另一种等效的表述:
multirember&Co a lat col = g a lat id id where
id x = x ; identity function
(f ∘ g) x = f (g x) ; function composition
g a [b, ...lat] c d
| b == a = g a lat c (d ∘ (x => cons b x)) ; (d ∘ {cons b})
| else = g a lat (c ∘ (x => cons b x)) d ; (c ∘ {cons b})
g a [] c d = col (c []) (d [])
因此
multirember&Co 1 [ 2, 1, 3, 1, 4, 5 ] col
=
col (((((id ∘ {cons 2}) ∘ {cons 3}) ∘ {cons 4}) ∘ {cons 5}) []) ; { } is for
( ( (id ∘ {cons 1}) ∘ {cons 1} ) []) ; partial application
=
col (id (cons 2 (cons 3 (cons 4 (cons 5 [])))))
(id (cons 1 (cons 1 []) ) )
这显然是一回事。
在另一个伪代码(带有列表推导)中,这表明自己是
multirember&Co a lat col
= col [ b for b in lat if (b /= a) ]
[ b for b in lat if (b == a) ]
= ( ((n,s) => col n s) ∘ {partition {/= a}} ) lat
除了只对列表进行一次lat
遍历(在原始代码中),高效地构建模仿原始列表结构的 lambda 函数的嵌套链;然后评估哪个链以创建两个结果,并将它们传递给最顶层的收集器函数col
。
所有这些都向我们展示了连续传递样式(就是这样)的力量,它实际上创建了自己的函数调用协议,例如,从每个递归函数调用传回两个结果,即使通常在 lambda 演算中函数只能有一个结果(即使是一对)。