Положение аргументов функции ядра Clojure кажется довольно запутанным.

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

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

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

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

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

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

Спасибо


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

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

Всего 3 ответа


Для некоторых функций (особенно функций, которые являются «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 ...) глобальную функцию.


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

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

Первичные операнды коллекции на первом месте. Таким образом, можно написать → и тому подобное, и их положение не зависит от того, имеют ли они переменные параметры арности. Существует традиция этого в языках 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). Когда функции последовательности варьируются, это обычно происходит из их источников.

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


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

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

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

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


Есть идеи?

10000