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

What is "named let" and how do I use it to implement a map function?

我是 Scheme 的新手,我正在尝试实现自己的地图功能。我试图在网上找到它,但是我遇到的所有问题都是关于 map 函数的一些复杂版本(例如将两个列表作为输入的映射函数)。

我找到的最佳答案在这里:(For-each and map in 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))))))

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

您发布的第一个代码片段确实是实现地图功能的一种方式。它使用一个命名的 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 here.

现在,对于您编写的地图函数,您已经差不多完成了。你最后两个案例有问题。如果列表不为空,您想获取第一个元素,将您的函数应用于它,然后将该函数应用于列表的其余部分。我认为你误解了你实际写下的内容。懒得细说了。

回想一下,条件从句是这样构成的:

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

您有任意数量的测试具有强制性后果。您的评估程序将执行 test1?,如果评估结果为 #t,它将作为整个条件的结果执行结果。如果 test1?test2? 失败,它将执行 elsebody.


旁注

Scheme 中的所有内容都是真实的,除了 #f(错误)。例如:

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

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


现在为你的条件。 cond 的第一个案例将测试 L 是否为空。如果计算结果为 #t,则您 return 空列表。这确实是正确的。在空列表上映射一些东西只是空列表。

第二种情况((f (car L)))字面意思是"if f is true, then return the car of L"。

else 案例表明 "otherwise, return the result mymap on the rest of my list L"。

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

(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 的新手,所以这个功能就可以了。尝试并理解它。然而,有更好更快的方法来实现这种功能。阅读 this page 以了解累加器函数和尾递归等内容。我不会在这里详细介绍所有内容,因为它 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)

或者您可以使用 named 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)