Stephen Cagle
Stephen Cagle

Reputation: 14524

Get Clojure argument list

I want something that gives me the sequence of actual values passed to a function, similar to the arguments value in a javascript function.

I am aware that I can grab the entire function argument list using

(defn fx [& args]
 args)

<= (fx {:a 1} 2)
=> ({:a 1} 2)

But this removes the arity on my function. I want to have something like

(defn fx [{:keys [a]} b]
 (MAGIC_FUNCTION_THAT_RETURNS_THE_ARGS_VALUES))

<= (fx {:a 1} 2)
=> ({:a 1} 2)

Is it possible to get a raw sequence of the values passed to a function?

Upvotes: 10

Views: 5676

Answers (6)

ChrisBlom
ChrisBlom

Reputation: 1279

You can use a macro to bind the arguments to symbol, _args in this example.

(defmacro defn-args [name args & body]
  `(defn ~name ~args
     (let [~'_args ~args]
       ~@body)))

You can then use _args in the body of the function to refer to the arguments:

user> (defn-args foo [{:keys [a b]} y z] _args)

user> (foo {:a 1 :b 10} 2 3)
[{:a 1, :b 10} 2 3]

Upvotes: 1

Ivaylo Petrov
Ivaylo Petrov

Reputation: 1172

Using argument destruction can help. The following works fine for me (as far as I know, it also works for old versions of clojure).

(defn example [ & [a b :as args]] [a b args])
(example 1 2) 
 => [1 2 (1 2)]

The key point is that you can destruct the argument after &. The drawback is that it is possible to call the function with more arguments than expected (for example (example 1 2 3) is a valid invocation. Special care should be taken if this might be a problem.

Note: I came across this question while I was searching for similar feature. I kept digging and using an idea from here and :as as it was suggested in this answer, I found a solution for my problem.

Upvotes: 4

Stephen Cagle
Stephen Cagle

Reputation: 14524

This is the best I could cook up.

(def ^:dynamic *arguments* nil)

(defn unstructure [form]
  (cond
   (or (vector? form) (map? form)) (gensym)
   (= form '&) '&
   :else form))

(defmacro bind-args-defn [name args & body]
  (let [simple-args (vec (map unstructure args))
        i (.lastIndexOf simple-args '&)
        [h r] (split-at (if (neg? i) (count simple-args) i) simple-args)
        r (drop 1 r)]
    `(defn ~name
       ~simple-args
       (binding [*arguments* (lazy-cat ~@(map vector h) ~@r)]
         (let [~args *arguments*]
           ~@body)))))

(bind-args-defn                            ;;my special val binding defn
 silly                                     ;;the name
 [{:keys [a]} [b & c] & d]                 ;;the arg vector
 {:vals *arguments* :a a :b b :c c :d d})  ;;the body

Obviously, this does not accept the full set of defn options (metadata, docstring, pre and post, arities, etc) that can be passed to defn, but I think it illustrates the idea.

It works by capturing the args vector, and then creating a simple-args vector of the same length as the original args but with no destructuring; using that as the defn argument vector. It then massages this simple-args vector into a sort of flat vector without &, which it assigns to *arguments*. *arguments* is then destructured using the original args vector. Kind of convoluted, but it does what I want at the moment.

> (silly {:a 1} [2 3 4] 5 6)
  {:vals ({:a 1} [2 3 4] 5 6), :a 1, :b 2, :c (3 4), :d (5 6)}

Upvotes: 0

bmaddy
bmaddy

Reputation: 895

I don't know of a way to do this as you describe, but depending on what you're wanting to do there are some options.

If you're wanting to ensure the function is only called with two arguments, consider a precondition:

(defn fx [& args]
  {:pre [(= 2 (count args))]}
  args)

user=> (fx 1 2)
(1 2)
user=> (fx 1 2 3)
AssertionError Assert failed: (= 2 (count args))  user/fx (NO_SOURCE_FILE:1)

If you're wanting to keep track of your intended arity of a function, but still have access to a vector of all args, you could add your own metadata:

(defn
  ^{:my.ns/arglists '([{:keys [a]} b])}
  fx [& args]
    args)

user=> (fx 1 2)
(1 2)
user=> (-> #'fx meta :my.ns/arglists first)
[{:keys [a]} b]

If you're just wanting access to the destructured values you described and access to an args value, you could use let:

(defn fx [{:keys [a]} b]
  (let [args [{:a a} b]]
    [a b args]))

user=> (fx {:a 1 :c 3} 2)
[1 2 [{:a 1} 2]]
user=> (fx {:a 1 :c 3} 2 4)
ArityException Wrong number of args (3) passed to: user$fx  clojure.lang.AFn.throwArity (AFn.java:437)

You could also do a combination of these.

Upvotes: 2

Jared314
Jared314

Reputation: 5231

By the time the function body is executed, the parameters have already been destructured. You could define your own defn macro and expose those values. I know Lighttable does this in their Instarepl to show the argument values.

Upvotes: 4

FUD
FUD

Reputation: 5184

Not very nice as it requires to pass params as a vector, but seems apt

user.main=> (defn fx [[{:keys [a] :as e} b :as o]] [a b e o])
#'user.main/fx
user.main=> (fx [{:a 1} 2])
[1 2 {:a 1} [{:a 1} 2]]
user.main=> 

Upvotes: 1

Related Questions