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. Я останусь заполнять детали как упражнение для читателя.: -)

Ответ 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)