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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With