计划和球拍中的案例和报价

case and quotation in scheme & racket

当我预期yeah:

时,我对这个球拍代码打印nay感到有点惊讶
(define five 5)
(case 5
  [(five) "yeah"]
  [else "nay"])

at the racket documentation for case更清楚:

The selected clause is the first one with a datum whose quoted form is equal? to the result of val-expr.

所以这是关于报价的。我很确定我还没有完全掌握 lisps 中的报价可以给我带来什么。我从宏和 AST 转换的角度理解它。但是我很困惑为什么它对 case 有帮助,例如..?

我也很好奇,有了case这个规范,我可以用它来实现我想要的(比较实际值,而不是引用值),还是我应该使用另一个构造来实现那? (cond,虽然严格来说更强大,但对于简单的情况更冗长,因为您必须在每个条件下重复谓词)。

我现在找不到参考,但是 case 语句在其不同的子句中使用了文字的、未计算的数据,因为它既是一个常见的用例,也更容易进行高效编译。 您可能会编写自己版本的 Clojure 的 condp 宏或自定义条件运算符来处理您的用例。

Racket 文档给出了这个语法:

(case val-expr case-clause ...)

哪里

case-clause     =       [(datum ...) then-body ...+]
                |       [else        then-body ...+]

让我们与您的示例进行比较:

(define five 5)
(case 5             ; (case val-expr
  [(five) "yeah"]   ;    [(datum) then-body1]
  [else "nay"])     ;    [else    then-body2])

我们看到 (five) 被解释为 (datum)。这意味着 five 是 一段数据(这里是一个符号),而不是一个表达式(稍后将被评估)。

你的例子是这样计算的:

首先计算表达式 5。结果是值 5。 现在我们一次看一个子句。第一个子句是 [(five) "yeah"]。 值 5 是否等于(在 equal? 的意义上)到 (five) 中的数据之一?没有,所以我们看下一个子句:[else "nay"]。它是一个 else 子句,所以表达式 "nay" 被求值,结果是值 "nay"case 表达式的结果因此是值 "nay".

注1:case-子句的左侧是基准(认为:它们被隐式引用)。

注2:val-expr的结果与使用equal?的子句数据进行比较。 (这与使用 eqv?.

的 Scheme 相反

更新

为什么要包含 case?让我们看看如何使用 cond:

编写示例
(define five 5)
(let ([val five])
    (cond 
      [(member val '(five))      "yeah"]
      [(member val '(six seven)) "yeah"]  ; added 
      [else                      "nay"])

这表明可以不用 case 而只使用 cond。 但是 - 哪个版本更容易阅读?

对于 case 表达式,很容易看出该值与哪些数据进行比较。 在这里必须仔细观察才能找到基准。同样在示例中,我们事先知道我们正试图在一些数据列表中找到值。一般来说,我们需要更仔细地检查 cond-表达式,以了解发生了什么。

简而言之:使用 case 表达式可以提高代码的可读性。

对于历史感兴趣的人:https://groups.csail.mit.edu/mac/ftpdir/scheme-mail/HTML/rrrs-1986/msg00080.html 讨论了 case 是使用 eqv? 还是 equal?

更新 2

我会尝试给出一个答案:

I'm still not clear on the quotation vs working simply on the values though. I'm wondering specifically why doing the quotation, why working on datum instead of working on values. Didn't get that bit yet.

两种方法都有道理。

为了论证,让我们看一下 case 在子句左侧使用表达式而不是数据的情况。同样遵循 Scheme 传统,我们假设 eqv? 用于比较。让我们称这样一个 ecase 的 case-expression(expression-case 的缩写)。

语法变为:

(ecase val-expr ecase-clause ...)

哪里

ecase-clause     =      [(expr ...)  then-body ...+]
                |       [else        then-body ...+]

您的示例现在变为:

(define five 5)
(ecase five
  [('five) "yeah"]
  [else    "nay")

这看起来还不错,结果是我们习惯的。 但是考虑这个例子:

(ecase '(3 4)
  [('five (list 3 4) "yeah"]
  [else              "nay")

结果将是 "nay"。计算表达式 '(3 4)(list 3 4) 得到的两个列表在 eqv? 的意义上不相等。 这表明如果选择使用 eqv? 进行比较,则在左侧提供可用的表达式将无济于事。唯一适用于 eqv? 原子值的值 - 因此也可以使用隐式引号并将左侧限制为数据。

现在,如果使用 equal?,那么在左侧使用表达式会更有意义。 case的原始Racket版本与Scheme中的相同(即使用eq?),后来改为使用equal?。如果 case 是从头开始设计的,我认为,表达式将被允许而不是数据。

唯一剩下的问题:为什么 Scheme 的作者选择 eqv? 而不是 equal? 进行比较?我的直觉是,原因是性能(这在过去比现在更重要)。来自 rrrs-authors 邮件列表的 post 链接提供了两个选项。如果你再深入一点,你可能会找到答案。

问题是 case 引入了 隐式 quote 形式,这导致您的示例适用于 'five (其值为 'five),而不是 five(其值为 5)。

正是因为这个问题,我几乎从不使用 case。相反,我使用球拍的 match form with the == 模式:

(define five 5)
(define (f x)
  (match x
    [(== five) "yeah"]
    [_ "nay"]))
(f 5) ; "yeah"
(f 6) ; "nay"

这仅在值 5 上生成 "yeah",正如您预期的那样。如果您希望它等于 fivesix 时 return "yeah",您可以使用 or 模式:

(define five 5)
(define six 6)
(define (f x)
  (match x
    [(or (== five) (== six)) "yeah"]
    [_ "nay"]))
(f 5) ; "yeah"
(f 6) ; "yeah"
(f 7) ; "nay"

如果你真的想匹配 quoted 个数据,你可以通过写一个 explicit quote 形式来实现。

(define (f x)
  (match x
    [(or 'five 'six) "yeah"]
    [_ "nay"]))
(f 5) ; "nay"
(f 6) ; "nay"
(f 7) ; "nay"
(f 'five) ; "yeah"
(f 'six) ; "yeah"

这些 quote 形式在您使用 case 时是隐含的和不可见的,潜伏在那里等待引起混乱。