Clojure Вопрос метапрограммирования (для новичка!)
Все, я начинаю смотреть на Clojure язык и задавал пару вопросов о том, что я пытаюсь сделать. Широкая цель состоит в том, чтобы псевдоним функции последовательности every?
- all?
. Я уверен, что есть функция или макрос, который выполняет псевдонимы (или что-то в этом роде), но я хотел посмотреть, возможно ли это с некоторыми базовыми конструкциями, которые я знаю до сих пор. Мой подход состоял в том, чтобы определить функцию с именем all?
, которая применяет свои аргументы к реализации every?
.
Мне интересно узнать, может ли это быть агностиком, поэтому я хотел бы параметрировать мою функцию псевдонима, чтобы взять два аргумента, новое имя (как ключевое слово) и старое имя (как ссылку на функцию). Стремясь к достижению этой цели, я столкнулся с двумя проблемами.
1) Определение именованных функций с помощью ключевых слов вызывает ошибки. По-видимому, он хочет clojure.lang.IObj
.
user=> (defn :foo "bar")
java.lang.ClassCastException: clojure.lang.Keyword cannot be cast to clojure.lang.IObj (NO_SOURCE_FILE:0)
Есть ли функция для добавления ключевого слова в IObj или другие средства для параметризации имени новой функции с некоторым предоставленным значением? (В Ruby, define_method среди других методов делает это)
irb(main)> self.class.instance_eval do
irb(main)* define_method(:foo) { "bar" }
irb(main)> end
=> #<Proc>
irb(main)> foo
=> "bar"
2) Соберите все аргументы функции в одну переменную. Даже основные функции, такие как (+ 1 2 3 4)
, принимают переменное количество аргументов. Все методы определения функций, которые я видел до сих пор, принимают определенное количество аргументов, не имея возможности просто агрегировать все в списке для обработки в теле функции. Еще раз, что я собираюсь сделать в Ruby, вот так:
irb(main)> def foo(*args)
irb(main)> p args
irb(main)> end
=> nil
irb(main)> foo(1, 2, 3)
[1, 2, 3]
=> nil
Спасибо за любую помощь, которую вы можете мне предоставить!
Ответы
Ответ 1
Я отвечу в пунктах, так как вопросы могут быть четко разделены на ряд отдельных вопросов.
-
Что-то, что неявно содержится в том, что следует соблюдать, но которое, возможно, требует пули: объекты верхнего уровня, созданные def
и Co. (и, в частности, defn
), Варс. Итак, что вы на самом деле хотите сделать, это псевдоним Var; функции - это просто регулярные значения, которые на самом деле не имеют имен (кроме того, что они могут иметь имя, привязанное к себе локально внутри своих тел, но это не имеет ничего общего с проблемой).
-
Существует действительно "макрос псевдонимов", доступный в Clojure - clojure.contrib.def/defalias
:
(use '[clojure.contrib.def :only [defalias]])
(defalias foo bar)
; => foo can now be used in place of bar
Преимущество этого над (def foo bar)
заключается в том, что он копирует метаданные (например, docstring); он даже кажется, что работает с макросами в текущем HEAD, хотя я помню ошибку, которая помешала этому в более ранних версиях.
-
Вары называются символами, а не ключевыми словами. Символьные литералы в Clojure (и другие Lisps) не начинаются с двоеточий (:foo
- это ключевое слово, а не символ). Таким образом, для определения функции, называемой foo
, вы должны написать
(defn foo [...] ...)
-
defn
- вспомогательный макрос, облегчающий создание новых функций, поддерживающих Vars, позволяя программисту использовать сочетание синтаксиса def
и fn
. Таким образом, defn
не может возникнуть для создания Vars с существующими значениями (которые могут быть функциями), как это требуется для создания псевдонимов; используйте defalias
или просто def
.
-
Чтобы создать вариационную функцию, используйте следующий синтаксис:
(fn [x y & args] ...)
x
и y
потребуются позиционные аргументы; остальные аргументы, переданные функции (любое их число), будут собраны в seq и доступны под именем args
. Вам не нужно указывать какие-либо "необходимые позиционные аргументы", если они не нужны: (fn [& args] ...)
.
Чтобы создать Var с вариационной функцией, используйте
(defn foo [x y & args] ...)
-
Чтобы применить функцию к некоторым аргументам, которые вы собрали в объект seqable (например, args
seq в приведенных выше примерах или, возможно, вектор & c.), используйте apply
:
(defn all? [& args]
(apply every? args))
-
Если вы хотите написать функцию для создания псевдонимов - в отличие от макроса - вам нужно будет изучить функции intern
, with-meta
, meta
- и, возможно, resolve
/ns-resolve
, в зависимости от того, должна ли функция принимать символы или Vars. Я останусь заполнять детали как упражнение для читателя.: -)
Ответ 2
Все, что вам нужно сделать, это связать все? функция для всех? символ, который выполняется через def:
(def all? every?)
Подробнее об этом см. в макросе Clojure, чтобы создать синоним для функции
Ответ 3
Не думайте, что я могу много добавить к существующим объяснениям здесь, кроме, возможно, заполнить пару пробелов в словаре путешественников Ruby по сбору и деструкции аргументов:
(defn foo [& args] ; Ruby: def foo(*args)
(println args))
user=> (foo 1 2 3)
(1 2 3)
(defn foo [& args]
(+ args))
user=> (foo 1 2 3)
java.lang.ClassCastException ; + takes numbers, not a list
(defn foo [& args]
(apply + args)) ; apply: as Ruby proc.call(*args)
user=> (foo 1 2 3)
6
(defn foo [& args]
(let [[a b & other] args] ; Ruby: a, b, *other = args
(println a b other)))
user=> (foo 1 2 3)
1 2 (3)