Положение аргументов функции ядра Clojure кажется довольно запутанным. Что за логика c за этим стоит? - PullRequest
1 голос
/ 27 марта 2020

Для меня, как нового Clojurian, некоторые основные функции кажутся довольно нелогичными и запутанными, когда дело доходит до порядка / позиции аргументов, вот пример:

> (nthrest (range 10) 5) 
=> (5 6 7 8 9)

> (take-last 5 (range 10)) 
=> (5 6 7 8 9)

Возможно, есть некоторые Правило / логика c за этим я пока не вижу?

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

Или я должен просто запомнить это как есть?

Спасибо


Слегка оффтоп c:

rand & rand-int VS random-sample - еще один пример, когда именование функций кажется непоследовательным, но это довольно редко используемая функция, поэтому это не имеет большого значения.

Ответы [ 3 ]

7 голосов
/ 28 марта 2020

На этот вопрос есть ответы на Clojure.org: https://clojure.org/guides/faq#arg_order

Каковы практические правила для порядка аргументов в основных функциях?

Первичные операнды коллекции на первом месте. Таким образом, можно написать → и тому подобное, и их положение не зависит от того, имеют ли они переменные параметры арности. Существует традиция этого в языках OO и Common Lisp (slot-value, aref, elt).

. Один из способов думать о последовательностях заключается в том, что они читаются слева и подаются справа:

<- [1 2 3 4]

Большинство функций последовательности используют и создают последовательности. Таким образом, один способ визуализировать это в виде цепочки:

map <- filter <- [1 2 3 4]

, и один из способов думать о многих функциях seq состоит в том, что они параметризованы некоторым образом:

(map f) <- (filter pred) <- [1 2 3 4]

Так функции последовательности берут свои источник (источники) последними и любые другие параметры перед ними, а частичные позволяют прямую параметризацию, как указано выше. Существует традиция этого в функциональных языках и Лиспе.

Обратите внимание, что это не то же самое, что последний первичный операнд. Некоторые функции последовательности имеют более одного источника (concat, interleave). Когда функции последовательности имеют переменные c, они обычно находятся в их источниках.

Адаптировано из комментариев Rich Hickey .

5 голосов
/ 28 марта 2020

Функции, которые работают с seqs, обычно имеют фактический seq в качестве последнего аргумента. (map, filter, remote и т. д. c.)

Для доступа и «изменения» отдельных элементов в качестве первого элемента берется коллекция: conon, asso c, get, update

Таким образом , вы можете использовать макрос (->>) последовательно с коллекцией, а также последовательно создавать преобразователи.

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

0 голосов
/ 27 марта 2020

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

(ns tst.demo.core
  (:use tupelo.core tupelo.test))

(dotest
  (let [dozen      (range 12)
        odds-1     (filterv odd? dozen)
        filter-odd (partial filterv odd?)
        odds-2     (filter-odd dozen) ]
    (is= odds-1 odds-2
      [1 3 5 7 9 11])))

Для других функций, Clojure часто следует за порядком «первым по важности» или «самым важным первым» (обычно они имеют одинаковый результат). Таким образом, мы видим примеры вроде:

(get <map> <key>)
(get <map> <key> <default-val>)

Это также показывает, что любые необязательные значения должны, по определению, быть последними (чтобы использовать аргументы rest). Это распространено в большинстве языков (например, Java).


Для записи я действительно не люблю использовать частичные функции, так как они имеют определяемые пользователем имена (в лучшем случае) или используются встроенными (больше общий). Рассмотрим этот код:

  (let [dozen   (range 12)
        odds    (filterv odd? dozen)

        evens-1 (mapv (partial + 1) odds)
        evens-2 (mapv #(+ 1 %) odds)
        add-1   (fn [arg] (+ 1 arg))
        evens-3 (mapv add-1 odds)]

    (is= evens-1 evens-2 evens-3
      [2 4 6 8 10 12]))

Также

Я лично нахожу это действительно раздражает , пытаясь разобрать код с помощью partial как с evens-1, особенно для случая пользовательских функций или даже стандартных функций, которые не так просты, как +.

Это особенно верно, если partial используется с 2 или больше аргументов

  • Для случая с 1 аргументом литерал функции, видимый для evens-2, гораздо более читабелен для меня.

  • Если 2 или более Аргументы присутствуют, пожалуйста создайте именованную функцию (локальную, как показано для evens-3) или обычную (defn some-fn ...) глобальную функцию.

...