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