[racket] Why does (case ...) quote its matching value expressions?

25 views
Skip to first unread message

J Arcane

unread,
Dec 22, 2014, 12:00:05 AM12/22/14
to Racket Users
Up with a horrible ear-ache this morning I decided to include a FizzBuzz example in Heresy, the Racket #lang I've been working on, and ran into an unexpected behavior in the (case ...) statement.

In many languages with case, you can make the testing value a constant, and then make the matching clauses actual calculations which then match against that constant. So when doing FizzBuzz in C-like languages you can do something like "switch 0" and then "case x % 3" for the matching clauses.

It turns out this doesn't work in Racket, because Racket quotes the values in the matching clauses so they do not evaluate. Specifically, it narrows down to doing this in (case/sequential-test ...): #`(equal? v 'k)

I can implement an alternate version that works as I expect (and will probably include it in Heresy) just by removing that quote in my version, but I was curious as to the reasoning behind this behavior and if perhaps there's some explanation for it that I may've missed.

Any insights appreciated,
John Berry 

Jon Zeppieri

unread,
Dec 22, 2014, 12:54:34 AM12/22/14
to J Arcane, Racket Users
`case` is like C's `switch` (but unlike similar constructs in other
C-like languages) in that the case labels must be known at compile
time. This allows the compiler to generate especially efficient code.
If the label values are densely distributed, a decent C compiler will
usually generate a jump table. Racket's `case` can't do that, since
it's not implemented at a low-enough level, but it can generate, in
the best case, a vector lookup followed by an open-coded binary
search. In the worst case, it will use a hash table lookup followed by
an open-coded binary search. (If the number of constant labels is
below a set threshold, then `case` will just test them in order,
exactly as if you'd written a `cond` expression. That's what
`case/sequential-test` does.)

C's switch does, however, allow the use of constant *expressions* as
case labels, so you can have something like `case FOO % 3`, where `FOO
% 3` can be computed at compile-time. (In your example of `case x %
3`, unless the value of `x` is known at compile time, it would be
illegal in C, though legal certain languages that use a similar
syntax.) Racket's `case`, on the other hand, only allows plain-old
values.

This is limiting sometimes. For example, to get the very best
performance out of `case`, you need to use either fixnums or chars,
but if you're creating, say, a state machine, you probably want to use
symbolic names for your states. In C, you'd define an integer constant
and use the symbolic name as your case label. You can't do that with
Racket's `case`, however; if you want to use symbolic names, then
you'll actually be representing your states with symbols rather than
fixnums.

At any rate, if you actually need to do runtime computation in your
state labels, then neither C's `switch` nor Racket's `case` are
appropriate.
> ____________________
> Racket Users list:
> http://lists.racket-lang.org/users
>
____________________
Racket Users list:
http://lists.racket-lang.org/users

Vincent St-Amour

unread,
Dec 22, 2014, 3:40:04 PM12/22/14
to Jon Zeppieri, Racket Users
As Jon said.

For that kind of use case, I recommend `match`. It's better than `case`
in almost every (all?) respect.

Vincent



At Mon, 22 Dec 2014 00:52:48 -0500,

Matthias Felleisen

unread,
Dec 22, 2014, 4:39:18 PM12/22/14
to J Arcane, Racket Users

Look for evcase. -- Matthias

J Arcane

unread,
Dec 22, 2014, 11:12:00 PM12/22/14
to Matthias Felleisen, Racket Users
Thanks for the insight. I hadn't considered the performance implications involved I suppose (the perpetual problem of the young Lisper ...)

And evcase does indeed work more like I expected, for example: 

(for ([n (in-range 1 101)])
  (evcase 0
          ((+ (modulo n 5)
              (modulo n 3)) (displayln "FizzBuzz"))
          ((modulo n 5) (displayln "Buzz"))
          ((modulo n 3) (displayln "Fizz"))
          (else (displayln n))))

Works exactly as I'd expect. I'll have a look at the code and see if any further insights might be found there for Heresy's (select case ...)

Thanks!

Matthias Felleisen

unread,
Dec 22, 2014, 11:21:26 PM12/22/14
to J Arcane, Racket Users

Keep in mind that case also isn't really cheap. There's an infinite number of symbols so there is no cheap jump table. -- Matthias

Eli Barzilay

unread,
Dec 23, 2014, 1:34:51 PM12/23/14
to Jon Zeppieri, Racket Users
On Mon, Dec 22, 2014 at 12:52 AM, Jon Zeppieri <zepp...@gmail.com> wrote:
> `case` is like C's `switch` (but unlike similar constructs in other
> C's switch does, however, allow the use of constant *expressions* as
> case labels, so you can have something like `case FOO % 3`, where `FOO
> % 3` can be computed at compile-time.

(define-syntax (case* stx)
(syntax-case stx ()
[(_ what [v e ...] ...)
(with-syntax ([(v* ...) (generate-temporaries #'(v ...))])
#'(let-syntax ([foo (λ(_) (with-syntax ([(v* ...) (list v ...)])
#'(case what [(v*) e ...] ...)))])
foo))]))

(case* 3
[1 2]
[(+ 1 2) 4])

--
((lambda (x) (x x)) (lambda (x) (x x))) Eli Barzilay:
http://barzilay.org/ Maze is Life!

J Arcane

unread,
Dec 24, 2014, 3:24:11 AM12/24/14
to Eli Barzilay, Racket Users
That is a lot more brief than mine. I just adapted a naive, unquoted version from the Racket case:

(define-syntax my-case
  (syntax-rules (else)
    [(_ val ((mtch ...) expr) rest ... (else expr2)) 
     (if (my-comp val (mtch ...))
         expr
         (my-case val rest ... (else expr2)))]
    [(_ val ((mtch ...) expr) (else expr2))
     (if (my-comp val (mtch ...))
         expr
         expr2)]
    [(_ val (else expr)) expr]
    [(_ val ((mtch ...) expr) rest ...)
     (my-case val ((mtch ...) expr) rest ... (else #f))]))

(define-syntax my-comp
  (syntax-rules ()
    [(_ v ()) #f]
    [(_ v (k)) (equal? v k)]
    [(_ v (k ks ...)) (if (equal? v k)
                          #t
                          (my-comp v (ks ...)))]))

Eli Barzilay

unread,
Dec 28, 2014, 6:48:37 PM12/28/14
to J Arcane, Racket Users
On Wed, Dec 24, 2014 at 10:19 AM, J Arcane <jar...@gmail.com> wrote:
> That is a lot more brief than mine. I just adapted a naive, unquoted version
> from the Racket case:

(The main point was that you can do compile-time computations, so it's a very
different thing...)

--
((lambda (x) (x x)) (lambda (x) (x x))) Eli Barzilay:
http://barzilay.org/ Maze is Life!
Reply all
Reply to author
Forward
0 new messages