Ключевое слово Clojure и необязательная проблема с аргументом - PullRequest
11 голосов
/ 26 ноября 2010

Я хочу создать функцию, которая принимает обязательный аргумент x и необязательный аргумент opt1 ИЛИ аргумент ключевого слова opt2.

Прямо сейчас у меня есть

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

Но указанная выше сигнатура позволяет мне передавать ключевое слово аргумента opt2 только в том случае, если x и opt1 присутствуют, например,

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

не так

(foo 'x {:opt2 'opt2})

Пожалуйста, помогите мне создать функцию, которая принимает требуемый аргумент X и либо opt1, либо opt2, где opt2 является аргументом ключевого слова.

Спасибо.

РЕДАКТИРОВАТЬ: Я хочу сделать то же самое для других макросов. Поэтому мне все еще нужно использовать defmacro.

Ответы [ 2 ]

15 голосов
/ 29 ноября 2010

Проблема в двусмысленности. Рассмотрим функцию (fn foo [x y & args]), которая принимает два необязательных аргумента, а затем любое количество аргументов ключевого слова. Если вы тогда называете это как (foo :bar :baz), как ваша программа справится с этим? x => :bar, y => :baz? Или x и y не предоставляются с одним :bar => :baz аргументом ключевого слова?

Даже в Common Lisp, который, возможно, обладает даже большей гибкостью, чем Clojure, при разборе параметров функций, смешивать необязательные аргументы и аргументы с ключевыми словами не рекомендуется, по крайней мере, одна популярная книга .

Лучше всего изменить все ваши аргументы на позиционные аргументы или все ваши параметры на аргументы ключевых слов. Если вы используете аргументы ключевого слова, вы можете использовать деструктуризацию хэш-карты, чтобы предоставить значения по умолчанию для «необязательных» параметров ключевого слова.

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
2 голосов
/ 26 ноября 2010

Вы должны проверить, являются ли дополнительные аргументы аргументами ключевых слов или нет (я предполагаю, что ваш или является эксклюзивным или), поэтому вы можете сделать это следующим образом:

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

проверить аргументы, если они являются ключевыми аргументами или нет. Поскольку у вас есть только один необязательный параметр, это легко: проверьте, есть ли только один, поскольку для аргументов ключевого слова требуется два.

...