Search code examples
functionvectorclojuremacros

Writing a Clojure #[...] Vector Function Macro


I want to write a Clojure vector macro form #[...] I can use like #[(inc %1) %2] that is equivalent to (fn [a b] [(inc a) b]) or #(vector (inc %1) %2).

Is this a special data reader, or can this be written with defmacro?


Solution

  • Just for a laugh, I wrote up what is probably the closest you're going to come to a solution. It doesn't define a reader macro, but it does define a macro that almost allows for the syntax you're looking for.

    I had to reinvent the wheel a bit to allow for % style implicit parameters since I couldn't get abusing #() to work:

    (defn arg-list
      "Returns a lazy-seq of (%, %2, %3, ...) strings."
      ([] (arg-list 1))
      ([n] (lazy-seq
             (cons (str "%" (if (> n 1) n))
                   (arg-list (inc n))))))
    
    (defmacro lambda
      "Use as you would #(). (map (lambda (* % 2)) [1, 2, 3])"
      [& body]
      (let [arg-syms (->> (arg-list) (take 5) (mapv symbol))]
        `(fn [& ~arg-syms]
           ~@body)))
    

    This defines a regular macro that mimics the #() reader macro. It evaluates to a variadic function that has the first 5 arguments in the list bound to "percentage" symbols. It allows for % style parameters:

    (mapv (lambda (* % %2)) [1 2 3] [1 5 9])
    [1 10 27]
    

    Then, you can just use that to define a macro that almost does what you're looking for:

    (defmacro vec-f [& elems]
      `(lambda (vec ~(vec elems))))
    

    (mapv (vec-f (inc %) %2) [1 2 3] [1 5 9])
    [[2 1] [3 5] [4 9]]
    

    But I can't say that I recommend its use.

    • It doesn't lend itself to readability.

    • Instead of throwing ArityExceptions, the missed arguments will just default to nil via destructuring

    • It only allows up to %5; although that's a hard-coded limit that can be increased. Using the current "var-arg method", I can't allow for an arbitrary number of parameters. Trying to realize an infinite list at compile time will obviously end in tears.

    As a proof of concept though, it shows what you're after is almost possible.

    You should just use

    #(vector (inc %1) %2)
    

    instead, or, as suggested in the comments

    #(do [(inc %1) %2])