可以在 if/cond 中使用 let 吗?

Possible to use a let within an if/cond?

我经常发现自己想要使用列表中的当前元素,而不总是需要 运行 一个过程来获取它,例如使用 elem 而不是 (car sequence) 每次我想引用序列中的当前元素。这有效...除非序列为空,因此我不能将 let 放在 null 检查之前。这是我试图做的'pattern':

(define (filter sequence)
  ; return a list of only those items where the filter predicate returns true
  (cond ((null? sequence) nil)
        ; now that we know we have an active sequence, I want to short-hand 'elem' and 'rest'
        (let (elem car sequence) 
              (rest cdr sequence))
          (display elem)))

但随后我收到一条错误消息 let: bad syntax。有没有办法以某种方式完成上述模式?

Upate:也许最好的方法就是在顶部添加一个 let 并进行简单的 if 检查?类似于:

(define (filter sequence)
  (let ((elem (if (null? sequence) nil (car sequence)))
       (rest (cdr sequence)))
    (cond ((null? sequence) nil)
          (display elem))))
  

一个 hack-ish 的方法是将 let 放在顶部——在条件之前,并为每个 let 变量添加 if 检查,因为示例:

(define (filter test sequence)
  (let ((elem (if (null? sequence) nil (car sequence)))
        (rest (if (null? sequence) nil (cdr sequence))))
    (cond ((null? sequence) nil)
          ((test elem) (cons elem (filter test rest)))
          (else (filter test rest)))))
(filter (lambda (x) (> x 2)) '(1 2 3 4 5))
; (3 4 5)

您的第一个代码片段的括号格式不正确。在这里使用 if 而不是 cond,可以按如下方式修复:

(define (filter sequence)
  ; return a list of only those items where the 
  ; filter predicate returns true
  (if (null? sequence) 
      'nil
      ; now that we know we have an active sequence, 
      ; I want to short-hand 'elem' and 'rest'
      (let ((elem (car sequence)) 
            (rest (cdr sequence)))
        (display elem)
        (filter rest))))

另请注意,我递归调用 filter。您需要修改它以接受谓词参数并实际过滤元素。

[这是我昨晚开始写的 coredump 的答案(我认为这是正确的)的附录,所以我会留给后人。]

从您的代码开始:

(define (filter sequence)
  (cond ((null? sequence) nil)
        (let (elem car sequence) 
              (rest cdr sequence))
          (display elem)))

这里至少存在三个问题:首先,你搞砸了 cond 的语法,即(部分)

(cond (<test> <expr> ...)
      ...
      (else <expr> ...))

因此,要修复此问题,您需要添加一个显式的 else 子句:

(define (filter sequence)
  (cond ((null? sequence) nil)
        (else
         (let (elem car sequence) 
           (rest cdr sequence))
         (display elem))))

现在的问题(这个问题的错误是出于某种原因掩盖了 cond 中的错误)是你搞砸了 let 的语法,这是(再次,在部分):

(let ((<var> <val>) ...) <expr> ...)

你在这里犯了两个不同的错误:你错过了绑定形式周围的括号,并且表达式根本不在 let 的正文中。因此,解决这些问题:

(define (filter sequence)
  (cond ((null? sequence) nil)
        (else
         (let ((elem car sequence)
               (rest cdr sequence))
           (display elem)))))

我们现在得到了最后的错误:对 carcdr 的调用没有括号。所以修复这些:

(define (filter sequence)
  (cond ((null? sequence) nil)
        (else
         (let ((elem car sequence)
               (rest cdr sequence))
         (display elem))))

好吧,现在这仍然不正确,那是因为你错过了对 carcdr:

调用的括号
(define (filter sequence)
  (cond ((null? sequence) nil)
        (else
         (let ((elem (car sequence))
               (rest (cdr sequence)))
           (display elem)))))

这现在有效,但它实际上并没有过滤序列。为此,它需要递归:

(define (filter sequence)
  (cond ((null? sequence) nil)
        (else
         (let ((elem (car sequence))
               (rest (cdr sequence)))
           (if (null? elem)
               (filter rest)
               (cons elem (filter rest)))))))

将内部条件提高到 cond:

可能会更好
(define (filter sequence)
  (cond ((null? sequence) nil)
        ((null (car sequence)) (filter (cdr sequence)))
        (else (cons (car sequence) (filter (cdr sequence))))))

尽管现在您遇到了多次调用 car & cdr 的问题,但我认为您希望避免这种情况。

如果您使用完整的 Racket,表达这一点的一个好方法是使用匹配,它可以很好地结合检查和绑定:

(define (filter sequence)
  (match sequence
    ['() '()]
    [(cons '() tail)
     (filter tail)]
    [(cons head tail)
     (cons head (filter tail))]))