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

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

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

(string->integer "10")
(string->integer "FF" 16)

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


person jcubic    schedule 08.07.2010    source источник


Ответы (6)


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

(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).

person Brian Carper    schedule 08.07.2010

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

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

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

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

or

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

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

person Matthew Gilliard    schedule 28.12.2011
comment
Так легко понять, если исходит из фона Python :) - person Dan; 06.03.2012
comment
Это гораздо легче понять, чем принятый ответ ... это принятый Clojurian способ? Пожалуйста, рассмотрите возможность добавления в этот документ. - person Droogans; 11.02.2013
comment
Я добавил проблему в неофициальное руководство по стилю, чтобы помочь решить эту проблему. . - person Droogans; 11.02.2013
comment
Этот ответ более эффективно отражает правильный способ сделать это, чем принятый ответ, хотя оба будут работать нормально. (конечно, великая сила языков Lisp состоит в том, что обычно существует много разных способов сделать одну и ту же фундаментальную вещь) - person johnbakers; 03.01.2014
comment
Мне это показалось многословным, и какое-то время у меня были проблемы с его запоминанием, поэтому я создал чуть менее подробный макрос. - person akbiggs; 22.05.2014
comment
Это решение, которое мне нравится больше всего. Однако я не совсем понимаю это. Почему вы можете деструктурировать остальные аргументы, если они не передаются в виде карты? Мои эксперименты REPL сообщают об остальных аргументах в виде списка, включая ключевые слова и значения, и я не могу их деструктурировать. - person Danielo515; 26.03.2018

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

(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)))
person metasoarous    schedule 01.10.2013
comment
Если вам не нужны значения по умолчанию, зависящие от других значений по умолчанию (или, возможно, даже если они есть), решение Мэтью выше также позволяет использовать несколько значений по умолчанию для разных переменных. Это намного чище, чем использование обычного or - person johnbakers; 03.01.2014
comment
Я новичок в Clojure, так что, возможно, OpenLearner прав, но это интересная альтернатива решению Мэтью, описанному выше. Я рад узнать об этом, решу ли я в конечном итоге использовать это или нет. - person GlenPeterson; 26.07.2014
comment
or отличается от :or, поскольку or не знает разницы между nil и false. - person Xiangru Lian; 28.07.2015
comment
@XiangruLian Вы говорите, что при использовании: или, если вы передадите false, он будет знать, что использовать false вместо значения по умолчанию? В то время как with или он будет использовать значение по умолчанию при передаче false, а не само значение false? - person Didier A.; 15.12.2015

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

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

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

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

(partial string->integer 10)

Чтобы сделать эту функцию вызываемой несколько раз, вы можете поместить ее в переменную, используя 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))

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

person optevo    schedule 19.07.2014
comment
Вопрос не в этом; хотя это интересно. - person Zaz; 07.01.2015

Вы также можете изучить (fnil) https://clojuredocs.org/clojure.core/fnil

person celwell    schedule 21.09.2015

Подход, очень похожий на предложение Мэтью, заключается в том, чтобы не использовать & rest args, а потребовать, чтобы вызывающие стороны предоставили единственный дополнительный аргумент карты гибких (и необязательных) ключей.

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

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

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

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

Небольшой недостаток заключается в том, что при вызове без параметров все равно должна быть предоставлена ​​пустая карта.

Это затрагивается в этом разделе позиционных аргументов (статья о запахах кода).

ОБНОВЛЕНИЕ: в Clojure 1.11 теперь есть поддержка передача карты для дополнительных аргументов (с использованием &).

person Micah Elliott    schedule 30.12.2020
comment
Требовалось сделать его необязательным. Возможно добавление &, но вы сказали, что в этом разница. - person jcubic; 31.12.2020