Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

case and quotation in scheme & racket

I was a bit surprised by this racket code printing nay when I expected yeah:

(define five 5)
(case 5
  [(five) "yeah"]
  [else "nay"])

Looking at the racket documentation for case makes it clearer:

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

So it's about quotation. I'm pretty sure that I did not yet fully grasp what quotation in lisps can buy me. I understand it in the viewpoint of macros and AST transformation. However I'm confused why is it helpful in the case of case for instance..?

I'm also curious, with this specification of case, can I use it to achieve what I wanted to (compare the actual values, not the quoted value), or should I use another construct for that? (cond, while strictly more powerful, is more verbose for simple cases, since you must repeat the predicate at each condition).

like image 340
Emmanuel Touzery Avatar asked Mar 11 '23 18:03

Emmanuel Touzery


2 Answers

The problem is that case introduces implicit quote forms, which cause your example to work for 'five (whose value is 'five), instead of five (whose value is 5).

I almost never use case because of exactly this problem. Instead I use racket's match form with the == pattern:

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

This produces "yeah" on only the value 5, just like you expected. If you wanted it to return "yeah" when it's equal to either five or six, you can use an or pattern:

(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"

And if you really want to match against quoted datums, you can do that by writing an explicit quote form.

(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"

These quote forms are implicit and invisible when you use case, lurking there waiting to cause confusion.

like image 173
Alex Knauth Avatar answered Mar 30 '23 04:03

Alex Knauth


The Racket documentation gives this grammar:

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

where

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

Let's compare to your example:

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

We see that (five) is interpreted as (datum). This means that five is a piece of data (here a symbol), not an expression (later to be evaluated).

Your example of case is evaluated like this:

First the expression 5 is evaluated. The result is the value 5. Now we look at a clause at a time. The first clause is [(five) "yeah"]. Is the value 5 equal (in the sense of equal?) to one of the datums in (five)? No, so we look at the next clause: [else "nay"]. It is an else-clause so the expression "nay" is evaluated and the result is the value "nay". The result of the case-expression is thus the value "nay".

Note 1: The left-hand sides of case-clauses are datums (think: they are implicitly quoted).

Note 2: The result of val-expr is compared to the clause datums using equal?. (This is in contrast to Scheme, which uses eqv?.

UPDATE

Why include case? Let's see how one can write the example using cond:

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

This shows that one could do without case and just use cond. However - which version is easier to read?

For a case expression it is easy to see which datums the value is compared to. Here one must look closely to find the datums. Also in the example we know beforehand that we are trying to find the value among a few list of datums. In general we need to examine a cond-expression more closely to see that's what's happening.

In short: having a case-expression increases readability of your code.

For the historically interested: https://groups.csail.mit.edu/mac/ftpdir/scheme-mail/HTML/rrrs-1986/msg00080.html disussed whether to use eqv? or equal? for case.

UPDATE 2

I'll attempt to given an answer to:

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.

Both approaches make sense.

Let's for the sake of argument look at the case where case uses expressions rather than datums in the left hand side of a clause. Also following the Scheme tradition, let's assume eqv? is used for the comparison. Let's call such a case-expression for ecase (short for expression-case).

The grammar becomes:

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

where

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

Your example now becomes:

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

This doesn't look too bad and the result is what we are used to. However consider this example:

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

The result of this would be "nay". The two lists resulting from evaluating the expressions '(3 4) and (list 3 4) are not equal in the sense of eqv?. This shows that if one chooses to use eqv? for comparisions, having expressions available on the left hand side won't be helpful. The only values that work with eqv? atomic values - and therefore one could just as well use implicit quotations and restrict the left hand side to datums.

Now if equal? was used it would make much more sense to use expressions on the left hand side. The original Racket version of case was the same as the one in Scheme (i.e. it used eq?) later on it was changed to used equal?. If case was designed from scratch, I think, expressions would be allowed rather than datums.

The only remaining issue: Why did the authors of Scheme choose eqv? over equal? for comparisons? My intuition is that the reason were performance (which back in the day was more important than now). The linked to post from the rrrs-authors mailing list gives two options. If you dig a little further you might be able to find responses.

like image 29
soegaard Avatar answered Mar 30 '23 05:03

soegaard