什么是"named let"以及如何使用它来实现地图功能?



我对Scheme完全陌生,我正在尝试实现我自己的映射函数。我试着在网上找到它,但我遇到的所有问题都是关于一些复杂版本的映射函数(比如以两个列表作为输入的映射函数)。

我找到的最好的答案是:(对于Scheme中的每个和地图)。这是这个问题的代码:

(define (map func lst)
(let recur ((rest lst))
(if (null? rest)
'()
(cons (func (car rest)) (recur (cdr rest))))))

不过,由于使用了一个模糊的函数recur,它并不能解决我的问题。这对我来说没有意义。

我的代码如下:

(define (mymap f L)
(cond ((null? L) '())
(f (car L))
(else (mymap (f (cdr L))))))

当用这种语言编程时,我确实理解函数方法背后的逻辑,但我在编码它时遇到了很大的困难

您发布的第一个代码片段实际上是实现map函数的一种方法。它使用一个命名的let。请参阅我在URL上对其工作方式的评论。它基本上是对递归函数的抽象。如果你要写一个打印从10到0的所有数字的函数,你可以像这个一样写

(define (printer x)
(display x)
(if (> x 0)
(printer (- x 1))))

然后称之为

(printer 10)

但是,由于它只是一个循环,您可以使用命名的let编写它:

(let loop ((x 10))
(display x)
(if (> x 0)
(loop (- x 1))))

正如Alexis King所指出的,这个命名的let是lambda的语法糖,它被立即调用。上面的构造相当于下面显示的片段。

(letrec ((loop (lambda (x) 
(display x)
(if (> x 0)
(loop (- x 1))))))
(loop 10))

尽管是letrec,但它并没有什么特别之处。它允许表达式(在本例中为lambda)调用自己。通过这种方式可以进行递归。关于letreclet的更多信息,请点击此处。

现在,对于您编写的map函数,您几乎已经完成了。你最后两个案子有问题。如果列表不为空,则要获取第一个元素,请将函数应用于该元素,然后将该函数应用于列表的其余部分。我认为你误解了你实际写下的内容。我会详细说明。

回想一下,条件子句是这样形成的:

(cond (test1? consequence)
(test2? consequence2)
(else   elsebody))

你有任何数量的测试都有强制性的结果。您的求值器将执行test1?,如果求值为#t,它将作为整个条件的结果执行结果。如果test1?test2?失败,它将执行elsebody


旁注

Scheme中除了#f(false)外,其他都是truthy。例如:

(if (lambda (x) x) 
1
2)

这个if测试将评估为1,因为if测试将检查(lambda (x) x)是否是真的。它是一个lambda。真值是在期望真值的表达式中(例如ifcond)将评估为真的值。


现在是你的第二个。cond的第一种情况将测试L是否为null。如果计算结果为#t,则返回空列表。这确实是正确的。在空列表上映射某些内容就是空列表。

第二种情况((f (car L)))的字面意思是"如果f为真,则返回L的car"。

else的情况说明"否则,返回我的列表L的其余部分上的结果mymap"。

我认为你真正想做的是使用if测试。如果列表为空,则返回空列表。如果它不为空,请将函数应用于列表的第一个元素。将函数映射到列表的其余部分,然后将应用函数的结果(列表的第一个元素)添加到该结果中。

(define (mymap f L)
(cond ((null? L) '())
(f (car L))
(else (mymap (f (cdr L))))))

所以你想要的可能看起来是这样的:

(define (mymap f L)
(cond ((null? L) '())
(else
(cons (f (car L)) 
(mymap f (cdr L))))))

使用if:

(define (mymap f L)
(if (null? L) '()
(cons (f (car L)) 
(mymap f (cdr L)))))

由于你是Scheme的新手,所以这个功能会很好。试着去理解它。然而,有更好更快的方法来实现这类功能。阅读本页可以了解累加器函数和尾部递归等内容。我不会详细介绍这里的一切,因为它1)不是问题,2)可能是信息过载。

如果您正在实施自己的列表过程,您可能应该确保他们在可能的情况下使用正确的尾部调用

(define (map f xs)
(define (loop xs ys)
(if (empty? xs)
ys
(loop (cdr xs) (cons (f (car xs)) ys))))
(loop (reverse xs) empty))
(map (λ (x) (* x 10)) '(1 2 3 4 5))
; => '(10 20 30 40 50)

或者,您可以使用命名的let表达式使其更加甜蜜,如原始代码中所示。然而,这一次使用了一个适当的尾部调用

(define (map f xs)
(let loop ([xs (reverse xs)] [ys empty])
(if (empty? xs)
ys
(loop (cdr xs) (cons (f (car xs)) ys)))))
(map (λ (x) (* x 10)) '(1 2 3 4 5))
; => '(10 20 30 40 50)

最新更新