Reputation: 14524
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
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
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
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
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
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
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