Можно ли разложить функцию Clojure?

Хотя я могу неправильно истолковывать концепцию гомойконичности, я понял ее как "код, являющийся данными".

Итак, я могу написать код так:

(def subject "world")
(def helo '(str "Hello " subject))

С этой точки зрения, helo это только данные, но они могут быть выполнены как код:

(eval helo)

который возвращает "Hello world".

Я также могу продолжать лечить helo как данные:

(first helo)
(count helo)

который возвращает соответственно str а также 3,

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

(defn helofn [subject]
  (str "Hello " subject))

Как мне разложить helofn? Кажется, я не могу воспринимать это как данные; если я сделаю это:

(count helofn)

Я получаю исключение:

java.lang.UnsupportedOperationException: количество не поддерживается для этого типа: пользователь $helofn

Есть ли другой способ разложения helofnили я просто слишком многого ожидаю от гомойконичности?

4 ответа

Решение

defn это просто макрос:

(macroexpand '(defn helofn [subject]
  (str "Hello " subject)))

(def helofn (clojure.core/fn ([subject] (str "Hello " subject))))

Если вы определите helofn как вы определили heloвы сможете обрабатывать его как данные:

(def helofn '(fn [subject]
  (str "Hello " subject)))

Теперь вы можете оценить и вызвать эту функцию:

((eval helofn) "world")

и рассматривать это как данные:

(count helofn)

Но когда вы используете defn макрос у вас ассоциируется helofn переменная с скомпилированной функцией, а не с ее кодом.

Это не просто функции. Допустим, вы определили hello со следующим кодом:

(def helo (str "Hello " subject))

Сейчас hello ассоциируется со строкой "Hello world", а не с (str "Hello " subject) код. Итак, теперь нет способа получить код, из которого была построена эта строка.

NB. Если вы хотите обрабатывать код clojure как данные, вам следует изучить его макросы. Любой код, переданный макросу, обрабатывается как данные, а любые данные, возвращаемые макросом, обрабатываются как код.

helofn определение - это данные, но вы позволяете им оцениваться (точно так же, как вы helo список). Если вы относились к определению так же, как heloтогда он останется данными и поддается любым преобразованиям, которые вы хотите применить:

(def helofndata '(defn helofn [subject]
                   (str "Hello " subject))

=> (second helofndata)
helofn
=> (eval helofndata)
#'user/helofn

Homoiconicity - очень мощная концепция, и я не думаю, что вы ожидаете от нее слишком многого.

defn на самом деле макрос, который использует def специальная форма для определения функции, так:

(defn sq [x]
  (* x x))

На самом деле эквивалентно:

(def sq (fn ([x] (* x x))))

Так defn здесь получает арги sq [x] (* x x), а затем строит список (def sq (fn ([x] (* x x)))), возвращает его как результат макроса и затем оценивается. Все это делается путем манипулирования списками, картами, векторами, символами и т. Д. defn макро.

Тот факт, что в Clojure вы не можете получить исходный список символов, из которого вы определили функцию, связан с тем, что в Clojure весь код компилируется. Вот почему оценка (fn [x] 1) в REPL возвращает что-то вроде #<user$eval809$fn__810 user$eval809$fn__810@10287d> , Но все же, как упоминалось в предыдущем ответе, код, который оценивается, является данными.

Может быть, я захожу слишком далеко с этим, но если вы хотите, чтобы для каждой определяемой вами функции были данные, из которых она была создана, вы могли бы добавить ее к метаданным, создав собственный настраиваемый макрос.

Вот наивная реализация для такого макроса:

(defmacro defn* [x & body ]
  (let [form `'~&form
        x    (vary-meta x assoc :form form)]
    `(defn ~x ~@body)))
;=> #'user/defn*

(defn* sq [x]
  (* x x))
;=> #'user/sq

(:form (meta #'sq))
;=> (defn* sq [x] (* x x))

&form является неявным аргументом (вместе с &env), который содержит всю (неоцененную) форму, с которой был вызван макрос (то есть данные, которые оцениваются компилятором).

Надеюсь, это поможет, и это не принесет больше путаницы.

Похоже, нет на основе

получить код функции clojure

а также

Можете ли вы получить "код как данные" загруженной функции в Clojure?

По сути, вы можете получить исходный код из функции, определенной в файле.clj, но нет надежного способа извлечь структуры данных, которые строили функцию из одной функции.

РЕДАКТИРОВАТЬ: Кроме того, я думаю, что вы ожидаете слишком многого от гомойконичности Сам код является данными да, но это довольно стандартно, чтобы не иметь возможности извлечь исходный исходный код на основе артефакта, испускаемого этим кодом. Например, когда у меня есть 2, у меня нет возможности узнать, что оно было создано (+ 1 1) или (- 4 2) таким же образом, как функция - это фрагмент данных, созданный путем вызова fn над некоторыми другими структурами данных, которые интерпретируются как код

Другие вопросы по тегам