Как создать значение по умолчанию для аргумента функции в Clojure

127

Я пришел с этим:

(defn string-> integer [str & [base]]
  (Целое число / parseInt str (if (nil? Base) 10 base)))

(строка-> целое число "10")
(строка-> целое число "FF" 16)

Но это должен быть лучший способ сделать это.

jcubic
источник

Ответы:

170

Функция может иметь несколько подписей, если подписи различаются по арности. Вы можете использовать это для предоставления значений по умолчанию.

(defn string->integer 
  ([s] (string->integer s 10))
  ([s base] (Integer/parseInt s base)))

Обратите внимание, что оба предположения falseи nilсчитаются не значениями, (if (nil? base) 10 base)их можно сократить до (if base base 10)или далее до (or base 10).

Брайан Карпер
источник
3
Думаю, было бы лучше во второй строке сказать (recur s 10), используя recurвместо повторения имя функции string->integer. Это упростило бы переименование функции в будущем. Кто-нибудь знает причину не использовать recurв таких ситуациях?
Рори О'Кейн,
10
Похоже, recurработает только с той же арностью. если вы пробовали повторять выше, например:java.lang.IllegalArgumentException: Mismatched argument count to recur, expected: 1 args, got: 2, compiling:
djhaskin987
Пробежался по этой же проблеме. Имеет ли смысл иметь сам вызов функции (т.е. (string->integer s 10))?
Курт Мюллер
155

Вы также можете деструктурировать restаргументы как карту, начиная с Clojure 1.2 [ ref ]. Это позволяет вам назвать и указать значения по умолчанию для аргументов функции:

(defn string->integer [s & {:keys [base] :or {base 10}}]
    (Integer/parseInt s base))

Теперь ты можешь позвонить

(string->integer "11")
=> 11

или

(string->integer "11" :base 8)
=> 9

Вы можете увидеть это в действии здесь: https://github.com/Raynes/clavatar/blob/master/src/clavatar/core.clj (например)

Мэтью Гиллиард
источник
1
Так легко понять, если исходит из фона Python :)
Дэн
1
Это гораздо легче понять, чем принятый ответ ... это общепринятый "клоджурский" способ? Пожалуйста, рассмотрите возможность добавления в этот документ.
Droogans
1
Чтобы решить эту проблему, я добавил проблему в неофициальное руководство по стилю.
Droogans
1
Этот ответ более эффективно отражает «правильный» способ сделать это, чем принятый ответ, хотя оба будут работать нормально. (конечно, великая сила языков Лиспа заключается в том, что обычно существует много разных способов сделать одну и ту же фундаментальную вещь)
johnbakers
2
Мне это показалось многословным, и какое-то время у меня были проблемы с его запоминанием, поэтому я создал немного менее подробный макрос .
akbiggs
33

Это решение ближе к духу оригинального решения , но немного чище.

(defn string->integer [str & [base]]
  (Integer/parseInt str (or base 10)))

Аналогичная картина , которая может быть удобно использование в orсочетании сlet

(defn string->integer [str & [base]]
  (let [base (or base 10)]
    (Integer/parseInt str base)))

Хотя в этом случае более подробный, он может быть полезен, если вы хотите, чтобы значения по умолчанию зависели от других входных значений . Например, рассмотрим следующую функцию:

(defn exemplar [a & [b c]]
  (let [b (or b 5)
        c (or c (* 7 b))]
    ;; or whatever yer actual code might be...
    (println a b c)))

(exemplar 3) => 3 5 35

Этот подход можно легко расширить для работы с именованными аргументами (как в решении М. Гиллиара):

(defn exemplar [a & {:keys [b c]}]
  (let [b (or b 5)
        c (or c (* 7 b))]
    (println a b c)))

Или используя еще больше фьюжн:

(defn exemplar [a & {:keys [b c] :or {b 5}}]
  (let [c (or c (* 7 b))]
    (println a b c)))
metasoarous
источник
Если вам не нужны значения по умолчанию, зависящие от других значений по умолчанию (или, возможно, даже если они есть), решение Мэтью выше также допускает несколько значений по умолчанию для разных переменных. Это намного чище, чем использование обычногоor
johnbakers
Я новичок в Clojure, так что, возможно, OpenLearner прав, но это интересная альтернатива решению Мэтью выше. Я рад узнать об этом, решу ли я в конечном итоге использовать это или нет.
GlenPeterson
orотличается от :orтак orкак не знает разницы между nilи false.
Xiangru Lian
@XiangruLian Вы говорите, что при использовании: или, если вы передадите false, он будет знать, что использовать false вместо значения по умолчанию? В то время как with или он будет использовать значение по умолчанию при передаче false, а не само значение false?
Дидье А.
8

Вы можете рассмотреть еще один подход: частичные функции. Возможно, это более «функциональный» и более гибкий способ указания значений по умолчанию для функций.

Начните с создания (при необходимости) функции, которая имеет параметр (ы), который вы хотите предоставить по умолчанию в качестве ведущего параметра (ов):

(defn string->integer [base str]
  (Integer/parseInt str base))

Это сделано потому, что версия Clojure partialпозволяет вам указывать значения «по умолчанию» только в том порядке, в котором они появляются в определении функции. После того, как параметры будут упорядочены по желанию, вы можете создать версию функции по умолчанию, используя partialфункцию:

(partial string->integer 10)

Чтобы сделать эту функцию вызываемой несколько раз, вы можете поместить ее в var, используя def:

(def decimal (partial string->integer 10))
(decimal "10")
;10

Вы также можете создать «локальное значение по умолчанию», используя let:

(let [hex (partial string->integer 16)]
  (* (hex "FF") (hex "AA")))
;43350

Подход с частичной функцией имеет одно ключевое преимущество перед другими: потребитель функции все еще может решить, какое будет значение по умолчанию, а не производитель функции, без необходимости изменять определение функции . Это проиллюстрировано в примере, в hexкотором я решил, что функция по умолчанию decimal- это не то, что мне нужно.

Еще одно преимущество этого подхода заключается в том, что вы можете присвоить функции по умолчанию другое имя (десятичное, шестнадцатеричное и т. Д.), Которое может быть более наглядным и / или иметь другую область действия (var, local). При желании частичную функцию также можно смешать с некоторыми из вышеперечисленных подходов:

(defn string->integer 
  ([s] (string->integer s 10))
  ([base s] (Integer/parseInt s base)))

(def hex (partial string->integer 16))

(Обратите внимание, что это немного отличается от ответа Брайана, поскольку порядок параметров был изменен на обратный по причинам, указанным в верхней части этого ответа)

optevo
источник
1
Вопрос не в этом; хотя это интересно.
Zaz