11

I want to create a function that takes in a required argument x, and either a optional argument opt1 OR a keyword argument opt2.

Right now I have

(defn foo x & [opt1 {:keys [opt2]}]
  ...

But the above signature only lets me pass in keyword argument opt2 when both x and opt1 is present like

(foo 'x 'opt1 {:opt2 'opt2})

not like this

(foo 'x {:opt2 'opt2})

Please help me create a function that takes a required argument X and either opt1 or opt2, where opt2 is a keyword argument.

Thank you.

EDIT: I want to do the same for other macros as well. So I still need to use the defmacro.

2
  • Consider using defnk from clojure.contrib.def instead of explicit destructuring. Commented Nov 26, 2010 at 20:15
  • defnk is deprecated in favor of more consistent built-in functionality as of 1.2. Commented Nov 29, 2010 at 12:47

2 Answers 2

15

The problem is ambiguity. Consider a function (fn foo [x y & args]) that takes two optional arguments and then any number of keyword arguments. If you then call it like (foo :bar :baz), how does your program handle it? x => :bar, y => :baz? Or x and y not provided, with a single :bar => :baz keyword argument?

Even in Common Lisp, which has arguably even more flexibility than Clojure in parsing function parameters, mixing optional and keyword arguments is not recommended, according to at least one popular book.

Your best bet is to change all of your arguments to positional arguments, or all of your parameters to keyword arguments. If you use keyword arguments, you can use hash-map destructuring to provide defaults for "optional" keyword parameters.

user> (defn foo [& {:keys [x y bar] 
                    :or {x 1 y 2 bar 3}}] 
        (prn [x y bar]))
#'user/foo
user> (foo)
[1 2 3]
nil
user> (foo :bar :baz)
[1 2 :baz]
nil
Sign up to request clarification or add additional context in comments.

Comments

2

you have to check if the aditional arguments are keyword arguments or not anyway (I assume your or is an exclusive or) so you can do it like this:

(defn foo [& args] 
    (if (= (count args) 1)
        (let [[opt1] args] (println opt1))
        (let [{:keys [opt2]} args] (println opt2))))

check the arguments if they are keyword arguments or not. As you only have one optional parameter it's easy: check if there's only one as keyword arguments require two.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.