Search code examples
clojure

Can't take value of a macro: #'clojure.core/case


I am creating a function within a function in Clojure, to simulate Java's objects concept where my function person acts like a constructor. (Just to wrap my head around using this concept in Clojure.)

(defn person [name age]
  (def p (fn [args & age]
    case args
     :set-name (person (first args) age)
     :set-age (person age)
    ;; :get-name (person (first args))
    ;; :get-age ()
)))

But I am getting the following error :

CompilerException java.lang.RuntimeException: Can't take value of a macro: #'clojure.core/case, compiling:(null:2:8)


Solution

    1. You (almost always) shouldn't use a def inside another def or defn. Using def is a global declaration and it should (nearly) always stand alone in the source file.

    See Clojure for the Brave & True for an intro online, or many good books like Getting Clojure.

    1. You forgot the parens around case:

    Example:

    (case args ...)
    
    1. Using case is really a special function designed primarily for Java object interop, and comes with some serious limitations (aka "optimizations"). It is nearly always better IMHO to use cond.

    Example:

    (cond
      (= :set-name args)   (person (first args) age)
      (= :set-age  args)   (person age)
      ...)
    

    Update

    One of the problems with case is that it must be a compile-time literal. However, this is not obvious and there is no warning if you try to use something that doesn't fit the definition. Then, you just get a silent failure. This example looks like it should return "my vec", but it fails & returns "default":

    ; "The test-constants are not evaluated.  They must be compile-time
    ; literals, and need not be quoted." 
    (let [myvec [1 2]]
      (case myvec
        []             "empty vec"
        (vec '(1 2))   "my vec"
        "default"))
    ;=> "default"
    

    So using case leaves a booby-trap ready to break your code without warning.