Есть ли в Clojure стандартная функция идентификации последовательности аргументов?

Есть ли в стандартной библиотеке Clojure функция, эквивалентная следующей?

(fn [& args] args)

Если нет, то почему?

Пример использования:

(take 10 (apply (fn [& args] args) (range)))
;=> (0 1 2 3 4 5 6 7 8 9)

;; ironically, map isn't lazy enough, so let's take it up to 11
(defn lazy-map [f & colls]
  (lazy-seq (cons (apply f (map first colls))
                  (apply lazy-map f (map rest colls)))))

(defn transpose [m]
  (apply lazy-map (fn [& args] args) m))

(defn take-2d [rows cols coll]
  (take rows (map (partial take cols) coll)))

(take-2d 3 3 (transpose (map (partial iterate inc) (range))))
;=> ((0 1 2) (1 2 3) (2 3 4))

Обратите внимание, что я не прошу преобразовательной, нетерпеливой функции, такой как vector или list.


person Sam Estep    schedule 23.11.2015    source источник
comment
Ваша функция точно эквивалентна list. Я не вижу разницы: что делает list преобразующим, а ваше нет?   -  person coredump    schedule 24.11.2015
comment
@coredump- Попробуйте (type ((fn [& args] args) :foo)) и (type (list :foo)).   -  person Sam Estep    schedule 24.11.2015
comment
@Elogent в идиоматическом clojure точный подтип редко важен - вы обнаружите, что две коллекции = и что они имеют одни и те же важные интерфейсы.   -  person noisesmith    schedule 24.11.2015
comment
Все аргументы оцениваются перед вызовом функции, поэтому я не очень понимаю, при чем тут лень. Но, может быть, вы могли бы немного уточнить, что вы имеете в виду?   -  person coredump    schedule 04.02.2016
comment
@coredump- оценивается, да, но если аргумент оказывается последовательностью, он не реализуется. Попробуйте первый пример в обновленном вопросе, заменив функцию идентификации последовательности на list или vector, и вы поймете, что я имею в виду.   -  person Sam Estep    schedule 04.02.2016
comment
@Elogent На самом деле никакой аргумент здесь не является последовательностью. Для любой функции fn при выполнении (apply fn (range)) вы вызываете fn с бесконечным числом непоследовательных аргументов. Но у вас есть точка зрения, и я отредактирую ответ, чтобы отразить этот конкретный аспект.   -  person coredump    schedule 04.02.2016
comment
Я был бы очень признателен, если бы участники голосования действительно оставили комментарий о том, почему этот вопрос плохой. Если что-то не так с моим мышлением, скажите мне, что это такое вместо того, чтобы просто проголосовать против вопроса и оставить меня ломать голову над тем, что я делаю неправильно.   -  person Sam Estep    schedule 04.02.2016


Ответы (2)


Такой функции нет, вы вольны реализовать и использовать ее по своему усмотрению:

(defn args [& args] args)

(set (map type (apply map args [[1 2 3][4 5 6][7 8 9]])))
=> #{clojure.lang.ArraySeq}

Почему он еще не доступен?

Это редко плодотворный вопрос: мы не только не знаем, что происходит в голове разработчиков, но и практически невозможно просить их обосновать или задокументировать, почему они не что-то сделали. Рассматривалось ли когда-нибудь добавление этой функции? Откуда мы можем знать? Есть ли на самом деле причина, или это произошло само собой?

С другой стороны, я согласен, что args кажется проще, потому что он передает уже существующую неизменяемую последовательность. Я также могу понять, если вы считаете, что лучше не преобразовывать аргументы в виде постоянного списка, хотя бы из соображений экономии.

Но это не так, как это реализовано, и накладные расходы на использование list действительно незначительны (и .java#L27" rel="nofollow">специализируется при сборке из экземпляра ArraySeq). Предполагается, что вы программируете интерфейс и никогда не заглядываете за кулисы, и с этой точки зрения list и args эквивалентны, даже если они не возвращают идентичные результаты. .

Вы добавили замечание о лени, и вы правы: если вам когда-нибудь понадобится взять аргументы из функции с переменным числом аргументов и передать их функции, которая работает с последовательностями, версия с list будет потреблять все заданные аргументы, тогда как args не будет. В некоторых случаях, как в случае с (apply list (range)), когда вы буквально передаете бесконечное количество аргументов, это может зависнуть навсегда.

С этой точки зрения маленькая функция args действительно интересна: вы можете переходить от аргументов к фактическим последовательностям, не создавая потенциальных проблем. Однако я не уверен, как часто это случается на практике. На самом деле, мне трудно найти вариант использования, когда лень в списке аргументов действительно имеет значение, когда дело касается args. В конце концов, чтобы передать бесконечную последовательность, единственный способ (?) — использовать apply:

(apply f (infinite))

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

(g (apply args (infinite)))

Но в этом случае мы могли бы напрямую вызвать:

(g (infinite))

В вашем примере g будет означать cons внутри lazy-map, но поскольку f задано на входе, мы не можем написать (cons (map ...) ...) напрямую. Таким образом, пример выглядит как реальный вариант использования args, но вы должны тщательно задокументировать функцию, потому что фрагмент, который вы дали, довольно сложен. Я склонен думать, что предоставление неограниченного количества аргументов функции — это запах кода: должна ли каждая функция с сигнатурой [& args] избегать использования всех аргументов, потому что данная последовательность может фактически быть бесконечной, как это делает lazy-map? Я бы предпочел, чтобы один аргумент был ленивой последовательностью для такого использования (и передал identity при необходимости) вместо всего списка аргументов, чтобы прояснить намерение. Но, в конце концов, я тоже не сильно против использования args.

Итак, в заключение, если вам не удастся убедить Рича Хикки добавить args в качестве основной функции, я уверен, что почти никто не захочет зависеть от внешней библиотеки, которая делает именно это1: это незнакомо, но также тривиально для реализации и в основном бесполезно. Единственная награда — это знать, что вы пропускаете небольшой шаг преобразования, который в большинстве случаев ничего не стоит. Точно так же не беспокойтесь о необходимости выбирать между вектором и списком: это практически не влияет на ваш код, и вы все равно можете изменить код позже, если докажете, что это необходимо. Что касается лени, хотя я согласен с тем, что перенос аргументов в списки или векторы может быть проблематичным с неограниченными списками аргументов, я не уверен, что проблема действительно возникает на практике.


1. Конечно, если оно когда-нибудь достигнет clojure.core, все тут же скажут, что это фундаментальная операция, которая очень полезна и определенно идиоматична ‹/cynic›.

person coredump    schedule 24.11.2015
comment
Отличный ответ, спасибо. У меня есть только еще один вопрос: использование списков для хранения общих данных не рекомендуется, поэтому может показаться, что vector — лучший вариант. Но поскольку векторы печатаются не так, как другие последовательности, у кого-то, кто играет с одной из моих функций в REPL, вполне может возникнуть соблазн рассматривать возвращаемое значение как вектор (и, например, вызывать для него get). Как мне выбрать между list и vector в этом случае? - person Sam Estep; 24.11.2015
comment
В руководстве по стилю, на которое вы ссылаетесь, есть много хороших рекомендаций, но некоторые из них лишены каких-либо обоснований. В частности, часть о списках довольно неоднозначна. Моя интерпретация такова: люди слышат, что Lisp — это все о списках, что неверно, и может возникнуть искушение запихнуть в них все, вместо того, чтобы использовать записи, карты и т. д., отсюда и предложение избегать использования списков по умолчанию. Но это верно и для векторов (обратите внимание, что в руководстве не говорится использовать вместо этого векторы). Не существует варианта, который был бы лучшим во всех случаях. В вашем примере вы можете использовать вектор, поскольку ввод состоит из векторов. - person coredump; 24.11.2015
comment
Большое спасибо за ваше терпение в этом вопросе. Я добавил еще один пример лени в ОП; подпадает ли моя функция transpose под недопустимый вариант использования для рассуждений args, которые вы объяснили? Если да, не могли бы вы обсудить это немного подробнее в своем ответе? Я слишком далеко захожу с этой ленью? - person Sam Estep; 07.02.2016
comment
@Elogent За вашим обновленным вариантом использования было немного сложно следить, честно говоря, но я понимаю, почему на самом деле там требуется args. Моя единственная проблема в том, что lazy-seq прилагает явные усилия, чтобы избежать доступа ко всем аргументам, что ИМХО редко встречается. Обычно функции принимают аргументы-параметры в виде конечного (небольшого) набора входных данных. - person coredump; 08.02.2016
comment
Я тоже примерно так и думал. Я полагаю, что вы достаточно подробно ответили на мой вопрос; большое спасибо за ваше понимание! :) - person Sam Estep; 08.02.2016

Есть функция identity. Он принимает аргумент и просто возвращает этот аргумент. Однако Clojure identity имеет одинарную арность.

Применение:

(identity 4) ;=> 4

(identity [1 2 3 4]) ;=> [1 2 3 4]

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

(defn varity-identity [& args]
    (map identity args))

(varity-identity 1 2 3 4 5) ;=> (1 2 3 4 5)

Надеюсь это поможет.

person turingcomplete    schedule 23.11.2015
comment
Я не понимаю, что вы имеете в виду. Я дал реализацию желаемой функции в своем вопросе, и ваша функция varity-identity даже не делает то же самое, что и эта функция (попробуйте вызвать type для результата, возвращаемого каждым). Это не отвечает на мой вопрос. - person Sam Estep; 24.11.2015
comment
varity-identity возвращает другой тип, LazySeq из-за использования карты, но делает то же самое, что и ваша функция. Обе функции принимают множество аргументов и возвращают эти аргументы в виде последовательности. если вы запустите (apply map varity-identity [[1 2 3] [4 5 6] [7 8 9]]), вы увидите, что он возвращает тот же результат, что и тот, который вы указали в своем вопросе. - person turingcomplete; 24.11.2015
comment
@Elogent Также в своем вопросе вы спрашиваете, есть ли в стандартной библиотеке функция, эквивалентная (fn [& args] args). Я объяснил identity и как вы можете использовать его, чтобы заставить его работать с переменной арностью, что дает тот же эффект, что и функция, которую вы предоставили. Поэтому я считаю, что ответил на то, о чем был задан вопрос. - person turingcomplete; 24.11.2015
comment
Я полагаю, что вы неправильно поняли слово эквивалент. Очевидно, что я могу делать все, что захочу, с инструментами в Clojure (см. определение функции, которое я написал в исходном вопросе), но это не то, о чем я прошу. Я спрашиваю, есть ли стандартная функция, которая делает точно то же самое, что и функция, которую я предоставил. Вы не привели ни такой функции, ни объяснения, почему ее не существует (учитывая тот факт, что применение функции к последовательности является собственная операция в IFn). - person Sam Estep; 24.11.2015
comment
@Elogent - я не думаю, что есть функция для создания ArraySeq, непосредственно похожей на list или vector, поскольку это похоже на деталь реализации, от которой вы не должны зависеть. Таким образом, вы можете использовать свою функцию или какой-либо другой конкретный тип seq. - person Lee; 24.11.2015
comment
@Lee Моя цель состоит не в том, чтобы использовать детали реализации, такие как ArraySeq, а скорее, поскольку иногда мне все равно, какой тип последовательности я создаю, чтобы избежать создания новых деталей реализации, выбирая между list, vector или каким-либо другим конкретным тип. Я также думаю, что такая функция сделает код, который ее использует, более читабельным и семантически значимым. - person Sam Estep; 24.11.2015
comment
Если вас не волнует тип последовательности, то что не так с list или vector? Ваша функция столь же преобразующая: (class (apply map (fn [& args] args) ['(1 2 3) '(4 5 6) '(7 8 9)])) ;=> clojure.lang.LazySeq, где '(1 2 3) — это clojure.lang.PersistentList. Все эти функции создают последовательности. Ваш не всегда возвращает один и тот же тип последовательности, но он должен сделать выбор, чтобы построить последовательность, и было бы необычно, чтобы он соответствовал классу некоторых из своих аргументов (см. Комментарий Noisesmith) . А [[1 2 3] '(4 5 6)]? - person Mars; 24.11.2015