Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make Clojure evaluate constant local expressions at compile time

Tags:

clojure

Pursuing 4Clojure Problem 178 - Best Hand, I have this for transforming card values from characters to numbers:

 (fn [ch]
   (or
    ({\A 1} ch)
    ((zipmap "TJQK" (iterate inc 10)) ch)
    (- (int ch) (int \0))))

The zipmap expression is evaluated on every call, always producing {\K 13, \Q 12, \J 11, \T 10}.

How can we make the compiler evaluate it just once?


After much brainbending, I came up with

(defmacro constant [exp] (eval exp))

... wrapping the zipmap call thus:

(constant (zipmap "TJQK" (iterate inc 10)))

I think this is equivalent to

(eval '(zipmap "TJQK" (iterate inc 10)))

... but not to eval without the quote:

(eval (zipmap "TJQK" (iterate inc 10)))

Corrections, comments, and improvements welcome.

like image 342
Thumbnail Avatar asked Apr 02 '14 13:04

Thumbnail


1 Answers

Your constant macro will work for this particular situation, because the form being evaluated has only symbols that resolve to functions in clojure.core and compile-time literals. You might run into problems with symbol resolution in other situations, such as computing local constants from function parameters to use in an anonymous function.

A more general way to do this is to move the call to zipmap outside the fn form. One option is to store the results of the zipmap call in a var using def, as in (def my-const (zipmap "TJQK" (iterate inc 10))).

However, in this case where you're making an anonymous function, creating a globally-accessible var might be overkill. So I'd suggest putting the fn inside a let binding that captures the constant value:

 (let [face-cards (zipmap "TJQK" (iterate inc 10))]
   (fn [ch]
     (or
       ({\A 1} ch)
       (face-cards ch)
       (- (int ch) (int \0)))))

Edit: As pointed out in the comments, this solution will compute the constant value at load-time, not at compile time. I have a hard time coming up with a scenario where that would matter, but it's worth pointing out that the semantics of it are slightly different than your approach.

like image 112
Alex Avatar answered Oct 06 '22 21:10

Alex