Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure Keyword and Optional Argument Problem

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.

like image 717
unj2 Avatar asked Nov 26 '10 18:11

unj2


2 Answers

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
like image 187
Brian Carper Avatar answered Nov 09 '22 12:11

Brian Carper


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.

like image 2
DaVinci Avatar answered Nov 09 '22 12:11

DaVinci