2012-05-21 3 views
8

Я иногда считаю удобным в Clojure определять уменьшенные версии функций, которые возвращают частичную функцию, например.Определение парциальных функций с уменьшенной arity

(defn prefix 
    ([pre string] 
    (str pre ":" string)) 

    ([pre] 
    (fn [string] 
     (prefix pre string)))) 

Это означает, что вы можете сделать что-либо:

(prefix "foo" 78979) 
=> "foo:78979" 

((prefix "foo") 78979) 
=> "foo:78979" 

Это кажется вполне Haskell-иш и исключает необходимость partial для создания частичных функций.

Но он считается хорошим стилем кодирования/дизайном API в Lisp?

+0

Это каррирование, да? Кажется полезным. И я думаю, что интерфейс здесь может быть улучшен. Было бы неплохо, если бы вы могли сделать эту технику общей; то есть для функций со всеми ключевыми словами, если вы вызываете эту функцию с неполным набором ключевых слов, она возвращает закрытие, которое принимает все оставшиеся ключевые слова. Если все ключевые слова потребляются, вы получаете значение функции. Я мог видеть, что это используется в качестве метода оптимизации. –

+0

Я думаю, что все еще идеоматично, если вы называете вас частичными для удобочитаемости, когда вы определяете их так: (def prefix (partial ...)). Таким образом, это имеет и следующее: имя для ссылки, и оно явное.Обратите внимание, что теперь вы не используете defn, но def-partial создает функцию для вас. – KIMA

ответ

7

В языках, где функции по умолчанию заданы, вызовы функций также отображаются. Когда один пишет (f a b c), язык интерпретирует это как (((f a) b) c). Это не относится к Clojure.

Я считаю, что делает кэрри функции в условиях, когда вызовы не кэррите по умолчанию создает концептуальное несоответствие - (. Я имею в виду человека читателей вашего кода) эта конструкция, вероятно, вызовет путаницу у читателей

Если функция имеет более 2 аргументов, определение становится уродливым быстро. Предположим, что функция имеет 4 аргумента. Чтобы полностью эмулировать вызов currying, вам нужно обрабатывать такие случаи, как ((f a b) c d), когда кто-то сначала передает 2 аргумента, а затем остальные два. В этом случае перегруженная версия функции с двумя аргументами должна возвращать перегруженную функцию, которая ведет себя по-разному в зависимости от того, получает ли она 1 или 2 аргумента. Я думаю, можно автоматизировать это с помощью макросов, но все же.

Кроме того, вы упускаете возможность определять аргументы по умолчанию и конструкцию & rest.

+0

аргументы по умолчанию и вариационные '& rest' функции – 6502

+0

@ 6502 Да. Отредактировано для полноты. –

6

hmmm ... лично, я бы предпочел посмотреть partial, так как это дает понять, что происходит.

Я не знаю, если это «хорошо» или «плохо» стиль кодирования, но я никогда не видел этот стиль, прежде чем в существующем коде Clojure, и я могу себе представить, что люди, использующие API будет ожидать как (prefix "foo" 78979) и (prefix "foo") к вернуть тот же объект.

Для того, чтобы разница между обе функции очистки вы могли бы сделать что-то вроде этого, вместо:

(defn prefix [pre string] 
    (str pre ":" string)) 

(defn prefix-fn [pre] 
    (fn [string] 
    (prefix pre string))) 

(prefix "foo" 78979)  ; => "foo:78979" 
((prefix-fn "foo") 78979) ; => "foo:78979" 
+2

«Я никогда не видел этот стиль раньше, чем в существующем коде Clojure» --- все это по новой библиотеке редукторов Хикки. –

+0

Вы правы, спасибо, что указали это. – Gert

9

Использование partial для создания каррированной функции основана на концепции Явное лучше (в большинстве случаев:)). И я обнаружил, что это понятие более применимо/используется в динамически типизированных языках, таких как Clojure, Python и т. Д., Может быть из-за отсутствия сигнатуры типа/статического ввода, имеет смысл сделать вещи ясными.

Смежные вопросы