Стандартная версия или идиоматическое использование (fn [f & args] (применить f args)) - PullRequest
12 голосов
/ 18 ноября 2011

Время от времени мне хочется применить набор функций к нескольким наборам параметров. Это легко сделать с картой и очень простой функцией.

(map
  (fn invoke [f & args] (apply f args))
  [- + *]
  [1 2 3]
  [1 2 3]
  [1 2 3])

 (-1 6 27)

При поиске в Интернете появляется довольно много библиотек, которые определяют похожую функцию, часто называемую funcall или invoke. Из-за склонности Clojure к различным функциям я не могу не думать, что уже должна быть версия этой функции по умолчанию.

Есть или есть другой идиоматический способ решения подобных ситуаций?

Edit:

Другая форма может быть

(map
  (comp eval list)
  [- + *]
  [1 2 3]
  [1 2 3]
  [1 2 3])

 (-1 6 27)

Что пугает меня из-за Eval.

Ответы [ 8 ]

10 голосов
/ 18 ноября 2011

Если вы действительно не имеете ни малейшего представления о названии функции, но знаете, каким должен быть ввод и вывод, вы можете попробовать https://github.com/Raynes/findfn.

(find-arg [-1 6 27] map '% [- + *] [1 2 3] [1 2 3] [1 2 3])
;=> (clojure.core/trampoline)

Это говорит нам о том, что

(map trampoline [- + *] [1 2 3] [1 2 3] [1 2 3])
;=> (-1 6 27)

На самом деле, вы можете злоупотреблять батутом как funcall в clojure.Но это вряд ли идиоматично, потому что это Лисп-1.Приведенный выше код оценивается как:

[(trampoline - 1 1 1), (trampoline + 2 2 2), (trampoline * 3 3 3)], который затем становится [-1 6 27] (точнее в форме a lazyseq).

Как указывает Адриан Муат в комментарии ниже, этовероятно, не самый предпочтительный способ ее решения.Использование funcall-подобной конструкции пахнет немного забавно.Должно быть более чистое решение.Пока вы не найдете это, findfn может быть полезным ;-).

7 голосов
/ 18 ноября 2011

Редактировать: это будет делать то, что вы хотите (как упомянуто @BrandonH):

(map #(apply %1 %&) [- + *] [1 2 3] [1 2 3] [1 2 3])

Но это вряд ли улучшение по сравнению с вашей версией - просто используется сокращение дляанонимные функции.


Насколько я понимаю, в Common Lisp необходимо FUNCALL, так как это Lisp-2, тогда как Clojure - это Lisp-1.

6 голосов
/ 19 ноября 2011

В стандартной библиотеке Clojure нет функции funcall или эквивалентной, которая работает именно так. «apply» довольно близко, но в конце нуждается в наборе аргументов, а не в чисто вариативном.

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

(map apply [- + *] [1 2 3] [1 2 3] [1 2 3] (repeat nil))
=> (-1 6 27)

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

(defn funcall [f & ps]
  (apply f ps))

(map funcall [- + *] [1 2 3] [1 2 3] [1 2 3])
=> (-1 6 27)
2 голосов
/ 19 ноября 2011

Лично я считаю, что ваша первая версия довольно ясна и идиоматична.

Вот альтернативный вариант, который вам может быть интересен:

(map 
  apply 
  [- + *] 
  (map vector [1 2 3] [1 2 3] [1 2 3]))

=> (-1 6 27)

Обратите внимание на хитрость использования (вектор карты....) для переноса последовательности аргументов в ([1 1 1] [2 2 2] [3 3 3]), чтобы их можно было использовать в функции apply.

2 голосов
/ 18 ноября 2011
(map #(%1 %2 %3 %4) [- + *][1 2 3][1 2 3][1 2 3])

(-1 6 27)

Проблема в том, что если вы хотите разрешить переменное число аргументов, синтаксис & помещает значения в вектор, что требует применения apply. Ваше решение выглядит хорошо для меня, но, как отмечает Брэндон Н, вы можете сократить его до #(apply %1 %&).

Как отметили другие авторы, это не имеет ничего общего с funcall, который, я думаю, используется в других Лиспах, чтобы избежать двусмысленности между символами и функциями (обратите внимание, что я назвал функцию здесь как (%1 ...), а не (funcall %1 ...).

1 голос
/ 20 ноября 2011

Другой подход, который не требует пояснений: «для каждой n-й функции примените его ко всем n-м элементам векторов»:

(defn my-juxt [fun-vec & val-vecs]
  (for [n (range 0 (count fun-vec))]
    (apply (fun-vec n) (map #(nth % n) val-vecs))))

user> (my-juxt [- + *] [1 2 3] [1 2 3] [1 2 3])
(-1 6 27)
0 голосов
/ 19 ноября 2011

А как насчет этого?Он выбирает соответствующие возвращаемые значения из juxt.Так как все это лениво, он должен рассчитывать только необходимые элементы.

user> (defn my-juxt [fns & colls] 
        (map-indexed (fn [i e] (e i))
          (apply map (apply juxt fns) colls)))
#'user/my-juxt
user> (my-juxt [- + *] [1 2 3] [1 2 3] [1 2 3])
(-1 6 27)
0 голосов
/ 18 ноября 2011

Сейчас я не могу воспользоваться функцией clojure.core, которую вы могли бы подключить к своей карте и заставить делать то, что вы хотите. Так что, я бы сказал, просто используйте свою собственную версию.

Мэтт, вероятно, прав в том, что причина, по которой нет funcall, заключается в том, что он вряд ли когда-нибудь понадобится в Lisp-1 (значение, функции и другие привязки имеют одно и то же пространство имен в clojure)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...