有问题的代码是:
(define multirember&co
(lambda (a lat col)
(cond
((null? lat)
(col (quote ()) (quote ())))
((eq? (car lat) a)
(multirember&co a
(cdr lat)
(lambda (newlat seen)
(col newlat
(cons (car lat) seen)))))
(else
(multirember&co a
(cdr lat)
(lambda (newlat seen)
(col (cons (car lat) newlat)
seen))))))
我整天都盯着这个,但我似乎无法理解它。当您重复使用该函数时,您将重新定义col
,但在示例中,它们似乎使用了原始定义。为什么不改变呢。如何在不传递参数newlat
和seen
的情况下重复使用。
很难解释我的问题,因为我似乎只是错过了一块。如果有人能够比书更明确地进行演练,我可能会理解它是如何运作的。
答案 0 :(得分:21)
让我们来看一个例子;也许这会有所帮助。 :-)为简单起见,我只是使用list
作为收集器/继续,它将返回一个带有延续参数的列表。
(multirember&co 'foo '(foo bar) list)
一开始,
a = 'foo
lat = '(foo bar)
col = list
在第一次迭代时,(eq? (car lat) a)
条件匹配,因为lat
不为空,lat
的第一个元素为'foo
。这样就设置了multirember&co
的下一个递归:
a = 'foo
lat = '(bar)
col = (lambda (newlat seen)
(list newlat (cons 'foo seen))
在下一次迭代中,else
匹配:由于lat
不为空,lat
的第一个元素为'bar
(而不是'foo
)。因此,对于下一次递归,我们有:
a = 'foo
lat = '()
col = (lambda (newlat seen)
((lambda (newlat seen)
(list newlat (cons 'foo seen)))
(cons 'bar newlat)
seen))
为了便于人类阅读(并避免混淆),我们可以重命名参数(由于词法作用域),而不会对程序的语义进行任何更改:
col = (lambda (newlat1 seen1)
((lambda (newlat2 seen2)
(list newlat2 (cons 'foo seen2)))
(cons 'bar newlat1)
seen1))
最后,(null? lat)
子句匹配,因为lat
现在为空。所以我们打电话给
(col '() '())
扩展为:
((lambda (newlat1 seen1)
((lambda (newlat2 seen2)
(list newlat2 (cons 'foo seen2)))
(cons 'bar newlat1)
seen1))
'() '())
(当替换newlat1 = '()
和seen1 = '()
时)变为
((lambda (newlat2 seen2)
(list newlat2 (cons 'foo seen2)))
(cons 'bar '())
'())
或(评估(cons 'bar '())
)
((lambda (newlat2 seen2)
(list newlat2 (cons 'foo seen2)))
'(bar)
'())
现在,替换值newlat2 = '(bar)
和seen2 = '()
,我们得到
(list '(bar) (cons 'foo '()))
或换句话说,
(list '(bar) '(foo))
给出我们的最终结果
'((bar) (foo))
答案 1 :(得分:7)
我在这里找到了一个很棒的答案: http://www.michaelharrison.ws/weblog/?p=34
我也一直在努力解决这个问题。关键是要理解词法范围(对我来说,javascript)和内部函数传递给eq上的multirember& co而不是eq分支。了解这一点,您将了解整个过程。
答案 2 :(得分:3)
上面的链接(http://www.michaelharrison.ws/weblog/?p=34)解释的是,整个练习是如何避免命令式编程(C,Java)需要声明两个“持有者”或“收集器”变量(或列表,向量)显式在内存中,以便在遍历列表时捕获您的答案。使用FP语言Scheme继续使用时,当您逐步(草莓金枪鱼和箭鱼)进入任何单独创建的“篮子”时,您不会“推”测试结果。相反,当你发送相应的consing函数时,你将两个列表组合在一起 - 一个用于eq?是的,另一个用于eq?假 - 通过复发。 。 。最后在第三个col函数结束,在TLS的第一个例子中,它是“a-friend”,它询问为保存所有匹配而构建的列表是否为空(null?)。 TLS然后要求你再次使用一个新的“last”col来“运行”multirember& co,它只询问包含所有“not tuna”原子的列表包含多少(“last-friend”)。因此,有两个“第一类”函数用于处理收集任务,即构建两个单独的列表,然后在递归展开结束时,原始col(“a-friend”)询问最终问题。也许名称“multirember& co”不是最大的名字,因为它确实没有重建列表减去要删除的原子;相反,它构建了两个单独的列表 - 永远不会显示 - 然后应用最终的col(朋友或最后的朋友)。 。 。它显示#t或#f,或“not tuna”列表的长度。
这是一些输出:
> (multirember&co 'tuna '(and tuna) a-friend)
#f
> (multirember&co 'tuna '(and not) a-friend)
#t
这是一个回馈不匹配列表的col:
(define list-not (lambda (x y) x))
及其用途:
> (multirember&co 'tuna '(and not) list-not)
(and not)
答案 3 :(得分:2)
我已经努力了解自己,了解multirember&co
内部发生的事情已有一段时间了。问题在于我认为我得到它的那一刻 - 下一个任务/例子证明我没有。
有什么帮助我整理了一个关于正在发生的事情的视觉表现(对于我来说,文字演练很难掌握,出于某种原因)。
所以,我把两个流程图放在一起:
一,只显示递归的不同步骤之间的关系:
另一个,反映实际值:
现在,每当我觉得我失去了一个争论的主线。再说一次,我只是参考这个流程图,它让我重回正轨。
在查看整个图片后,我已经理解了另一件事。通过流程图,a-friend
函数只是检查seen
是否包含任何值(尽管它在#f
中有值seen
时返回它#t
当seen
为空时,{} {{1}},这可能会造成混淆。
P.S。:我做了similar flow-charts for evens-only*&co
,后面会出现在本书中。
答案 4 :(得分:1)
代码不会像通常那样构建解决方案,但它会构建一个计算解决方案的代码,就像使用低级别操作构建树一样,cons
,{{1} },+
等,而不是使用高级累加器或过滤器。
这就是为什么难以说过程是迭代还是递归的原因,因为通过迭代过程的定义,它们使用有限数量的内存用于本地状态。但是,这种进程使用了大量内存,但这是在环境中分配的,而不是在本地参数中分配。
首先,我在这里复制代码,以便能够看到对应而不会滚动太多:
-
让我们尝试分解问题,看看究竟发生了什么。
(define multirember&co
(lambda (a lat col)
(cond
((null? lat)
(col (quote ()) (quote ())))
((eq? (car lat) a)
(multirember&co a
(cdr lat)
(lambda (newlat seen)
(col newlat
(cons (car lat) seen)))))
(else
(multirember&co a
(cdr lat)
(lambda (newlat seen)
(col (cons (car lat) newlat)
seen)))))))
这是一个微不足道的案例,它永远不会循环。
现在有趣的案例:
(multirember&co 'a
'()
(lambda (x y) (list x y)))
is the same as
(let ((col (lambda (x y) (list x y))))
(col '() '()))
在这种情况下,进程会生成此代码作为结果,并最终对其进行求值。请注意,本地它仍然是尾递归的,但是全局它是一个递归过程,它需要内存而不是通过分配一些数据结构,而是让评估器只分配环境帧。每个循环通过添加1个新帧来加深环境。
(multirember&co 'a
'(x)
(lambda (x y) (list x y)))
is the same as
(let ((col
(let ((col (lambda (x y) (list x y)))
(lat '(x))
(a 'a))
(lambda (newlat seen)
(col (cons (car lat) newlat)
seen)))))
(col '() '()))
这会构建代码,但在另一个分支上,会在另一个变量中累积结果。
所有其他情况都是这三种情况中的一种情况的组合,通过添加新图层可以清楚地看出每种情况如何起作用。
答案 5 :(得分:1)
让我们使用一些等式伪代码,为清楚起见省略了一些括号(因此,我们为调用f x y
编写(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]
和b = 1
的{{1}}。因此,这只是一个三个方程式:
当第二个参数是一个空列表时,“收集器”函数lat = [2,3]
会立即输入两个空列表作为其两个参数;
当第二个参数(列表) head 元素与第一个参数相同时,结果与递归尾部的结果相同
col
和n
, - 将将当前头元素(s
)添加到a
列表中,并将两个列表提供给此调用的收集器函数s
;
否则,在构造的收集器收到col
和n
之后,head元素将被添加到n
列表中,并且两者将被进一步输入集电器功能。
换句话说,我们正在处理从递归调用返回的两个结果,如果头部为s
则将头部置于第二个结果,如果头部为a
,则将头部置于第二个结果之前。
因此呼叫
(g 1 [ 2, 1, 3, 1, 4, 5 ] col)
与(将导致)调用相同
(col [ 2, ...[3, ...[4, ...[5, ...[]]]]]
[ 1, ...[1, ...[]] ])
即
(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
。
所有这些向我们展示了Continuation-Passing Style(这就是这个)的强大功能,实际上,它创建了自己的函数调用协议,这里例如从每个函数传回两个结果递归函数调用,即使通常在lambda演算中,函数也只能有一个结果(即使是一对)。
答案 6 :(得分:0)
我希望这次演练有助于
正如克里斯所说,我已经将newlat / seen重命名为n / s并添加了一个索引。 这本书为这些功能提供了可怕的名字(一位朋友的新朋友最新炒),所以我只保留L(对于lambda)和定义。
multirember&co 'tuna '(strawberries tuna and swordfish) a-friend)
multirember&co 'tuna '(tuna and swordfish) (L(n1 s1)(a-friend (cons 'strawberries n1) s1))
multirember&co 'tuna '(and swordfish) (L(n2 s2)((L(n1 s1)(a-friend (cons 'strawberries n1) s1)) n2 (cons 'tuna s2))
multirember&co 'tuna '(swordfish) (L(n3 s3)((L(n2 s2)((L(n1 s1)(a-friend (cons 'strawberries n1) s1)) n2 (cons 'tuna s2)) (cons 'and n3) s3))
multirember&co 'tuna '() (L(n4 s4)((L(n3 s3)((L(n2 s2)((L(n1 s1)(a-friend (cons 'strawberries n1) s1)) n2 (cons 'tuna s2)) (cons 'and n3) s3)) (cons 'swordfish n4) s4))
((lambda(n4 s4)((lambda(n3 s3)((lambda(n2 s2)((lambda(n1 s1)(a-friend (cons 'strawberries n1) s1)) n2 (cons 'tuna s2))) (cons 'and n3) s3)) (cons 'swordfish n4) s4)) '() '())
((lambda(n3 s3)((lambda(n2 s2)((lambda(n1 s1)(a-friend (cons 'strawberries n1) s1)) n2 (cons 'tuna s2))) (cons 'and n3) s3)) '(swordfish) '())
((lambda(n2 s2)((lambda(n1 s1)(a-friend (cons 'strawberries n1) s1)) n2 (cons 'tuna s2))) '(and swordfish) '())
((lambda(n1 s1)(a-friend (cons 'strawberries n1) s1)) '(and swordfish) '(tuna))
(a-friend '(strawberries and swordfish) '(tuna))