multiple arity in defmacro of clojure

后端 未结 2 622
孤独总比滥情好
孤独总比滥情好 2020-12-13 19:58

I encountered a strange problem relating to defmacro in Clojure, I have code like

(defmacro ttt
  ([] (ttt 1))
  ([a] (ttt a 2))
  ([a b] (ttt a b 3))
  ([a          


        
2条回答
  •  时光取名叫无心
    2020-12-13 20:47

    Macros have two hidden arguments

    Macros have two hidden arguments &form and &env that provide additional information about invocation and bindings that are the cause of the arity exception here. To refer to other arity versions within the same macro, use quasi-quote expansion.

    (defmacro baz
      ([] `(baz 1))
      ([a] `(baz ~a 2))
      ([a b] `(baz ~a ~b 3))
      ([a b c] `(println ~a ~b ~c)))
    
    user=> (macroexpand-1 '(baz))
    (clojure.core/println 1 2 3)
    
    user=> (baz)
    1 2 3
    nil
    

    Arity exception messages subtract the hidden arguments from the count

    The reason you get the (-1) arity exception is because the compiler subtracts these two hidden arguments when generating the error message for the general macro usage. The true message here for your first version of ttt would be "Wrong number of args (1)" because you supplied one argument a but the two additional hidden arguments were not provided by self-invocation.

    Multi-arity macros not common in the wild

    In practice, I suggest avoiding multi-arity macros altogether. Instead, consider a helper function to do most of the work on behalf of the macro. Indeed, this is often a good practice for other macros as well.

    (defn- bar
      ([] (bar 1))
      ([a] (bar a 2))
      ([a b] (bar a b 3))
      ([a b c] `(println ~a ~b ~c)))
    
    
    (defmacro foo [& args] (apply bar args))
    
    user=> (macroexpand-1 '(foo))
    (clojure.core/println 1 2 3)
    
    user=> (foo)
    1 2 3
    nil
    

    Macro expansion is recursive

    Your second ttt version works as well due to the recursive nature of macro-expansion

    user=> (macroexpand-1 '(ttt))
    (user/ttt 1)
    user=> (macroexpand-1 *1)
    (user/ttt 1 2)
    user=> (macroexpand-1 *1)
    (usr/ttt 1 2 3)
    user=> (macroexpand-1 *1)
    (clojure.core/println 1 2 3)
    

    So,

    user=> (macroexpand '(ttt))
    (clojure.core/println 1 2 3)
    

提交回复
热议问题