换句话说,“Okay, so code is data...”
该线程解决了如何从源文件中读取,但我想知道如何将已经加载的函数的s表达式转换为我可以读取和操作的数据结构。
换句话说,如果我说,
(defn example [a b] (+ a b))
我不能在运行时获取该列表吗?这不是“代码作为数据”的重点吗?
这是一个普遍的Lisp问题,但我正在寻找Clojure的答案。
答案 0 :(得分:7)
您可以使用clojure.repl/source
宏来获取符号来源:
user> (source max)
(defn max
"Returns the greatest of the nums."
{:added "1.0"
:inline-arities >1?
:inline (nary-inline 'max)}
([x] x)
([x y] (. clojure.lang.Numbers (max x y)))
([x y & more]
(reduce1 max (max x y) more)))
nil
但这只是答案的一部分。 AFAICT source
查找定义给定符号的源文件名和行号,然后从文件中打印源代码。因此,source
将不适用于您没有源代码的符号,即AOT编译的clojure代码。
回到原来的问题,您可以将source
视为读取与给定符号关联的元数据并简单地打印它。即这是作弊。它不会以任何方式将“代码作为数据”返回给您,其中代码我的意思是编译的clojure函数。
在我看来,“作为数据的代码”是指lisps的特性,其中源代码实际上是一个lisp数据结构,因此它可以被lisp阅读器读取。也就是说,我可以创建一个有效的lisp代码的数据结构,以及eval
那个。
例如:
user=> (eval '(+ 1 1))
2
此处'(+ 1 1)
是一个文字列表,由clojure读者读取,然后作为clojure代码进行评估。
更新: Yehonathan Sharvit在其中一条评论中询问是否可以修改函数的代码。以下代码片段读入函数的源代码,修改生成的数据结构,最后评估数据结构,从而导致定义新函数my-nth
:
(eval
(let [src (read-string (str (source-fn 'clojure.core/nth) "\n"))]
`(~(first src) my-nth ~@(nnext src))))
syntax-quote
行将nth
替换为my-nth
表格中的defn
。
答案 1 :(得分:2)
您可以使用source
函数获取最近版本的clojure中的源代码。
user=> (source nth)
(defn nth
"Returns the value at the index. get returns nil if index out of
bounds, nth throws an exception unless not-found is supplied. nth
also works for strings, Java arrays, regex Matchers and Lists, and,
in O(n) time, for sequences."
{:inline (fn [c i & nf] `(. clojure.lang.RT (nth ~c ~i ~@nf)))
:inline-arities #{2 3}
:added "1.0"}
([coll index] (. clojure.lang.RT (nth coll index)))
([coll index not-found] (. clojure.lang.RT (nth coll index not-found))))
nil
将字符串作为值,您可以将其包含在with-out-str
:
user=> (with-out-str (source nth))
"(defn nth\n \"Returns the value at the index. get returns nil if index out of\n bounds, nth throws an exception unless not-found is supplied. nth\n also works for strings, Java arrays, regex Matchers and Lists, and,\n in O(n) time, for sequences.\"\n {:inline (fn [c i & nf] `(. clojure.lang.RT (nth ~c ~i ~@nf)))\n :inline-arities #{2 3}\n :added \"1.0\"}\n ([coll index] (. clojure.lang.RT (nth coll index)))\n ([coll index not-found] (. clojure.lang.RT (nth coll index not-found))))\n"
user=>
答案 2 :(得分:0)
那是我的信息;很高兴认识你;-)顺便说一句,在那个帖子中给出的答案是很好的阅读;所以,如果你有兴趣,你可能想花点时间阅读它们。回到你的问题虽然source
似乎适用于通过文件加载的代码,但它并不适用于所有情况。我认为,具体而言,它不适用于repl中定义的函数。
user=> (def foo (fn [] (+ 2 2)))
#'user/foo
user=> (source foo)
Source not found
nil
user=> (defn foo2 [] (+ 2 2))
#'user/foo2
user=> (source foo2)
Source not found
nil
挖掘一点......
user=> (source source)
(defmacro source
"Prints the source code for the given symbol, if it can find it.
This requires that the symbol resolve to a Var defined in a
namespace for which the .clj is in the classpath.
Example: (source filter)"
[n]
`(println (or (source-fn '~n) (str "Source not found"))))
nil
user=> (source clojure.repl/source-fn)
(defn source-fn
"Returns a string of the source code for the given symbol, if it can
find it. This requires that the symbol resolve to a Var defined in
a namespace for which the .clj is in the classpath. Returns nil if
it can't find the source. For most REPL usage, 'source' is more
convenient.
Example: (source-fn 'filter)"
[x]
(when-let [v (resolve x)]
(when-let [filepath (:file (meta v))]
(when-let [strm (.getResourceAsStream (RT/baseLoader) filepath)]
(with-open [rdr (LineNumberReader. (InputStreamReader. strm))]
(dotimes [_ (dec (:line (meta v)))] (.readLine rdr))
(let [text (StringBuilder.)
pbr (proxy [PushbackReader] [rdr]
(read [] (let [i (proxy-super read)]
(.append text (char i))
i)))]
(read (PushbackReader. pbr))
(str text)))))))
nil
所以是的,看起来它试图从类路径加载源文件来尝试为你吐出来。我在使用Clojure时学到的一件事是,在10次中有9次看到源代码是有用的。