2013-05-23 6 views
6

Хотя я могу неправильно интерпретировать концепцию гомоконичности, я понял это как «код, являющийся данными».Можно ли разложить функцию Clojure?

Таким образом, я могу писать код, как это:

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

На данный момент, helo только данные, но и может быть выполнен в виде кода, как это:

(eval helo) 

, который возвращает «Привет мир ».

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

(first helo) 
(count helo) 

который возвращает соответственно str и 3.

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

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

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

(count helofn) 

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

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

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

ответ

6

defn просто macro:

(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 и позвоните по этой функции:

((eval helofn) "world") 

и относиться к нему как данные:

(count helofn) 

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

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

(def helo (str "Hello " subject)) 

Теперь hello ассоциируется с «привет мир» строки, а не с (str "Hello " subject) кодом. Итак, теперь нет способа получить код, с которым была построена эта строка.

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

1

Это не похоже, не основанный на

get a clojure function's code

и

Can you get the "code as data" of a loaded function in Clojure?

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

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

9

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

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

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

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 [email protected]> . Но все же, как упоминалось в previous answer, код, который оценивается , составляет.

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

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

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

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

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

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

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