Can you get the “code as data” of a loaded function in Clojure?
换句话说,"好吧,代码就是数据……"
该线程解决了如何从源文件读取的问题,但是我想知道如何将已经加载的函数的s表达式转换为可以读取和操作的数据结构。
换句话说,如果我说
1 | (defn example [a b] (+ a b)) |
我无法在运行时获取该列表? 这不是"代码即数据"的重点吗?
这确实是一个一般的Lisp问题,但我正在Clojure中寻找答案。
您可以使用
1 2 3 4 5 6 7 8 9 10 11 | 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
回到最初的问题,您可以将
在我看来,"代码即数据"是指lisps的功能,其中源代码实际上是lisp数据结构,因此可以由lisp阅读器读取。也就是说,我可以创建一个有效的lisp代码的数据结构,然后
例如:
1 2 | user=> (eval '(+ 1 1)) 2 |
这里的
更新:Yehonathan Sharvit在评论之一中询问是否可以修改功能代码。以下代码片段从函数中读取源代码,修改结果数据结构,最后评估数据结构,从而定义了新函数
1 2 3 4 | (eval (let [src (read-string (str (source-fn 'clojure.core/nth)"\ "))] `(~(first src) my-nth ~@(nnext src)))) |
您可以使用
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
要获取字符串作为值,可以将其包装在
1 2 3 4 5 6 7 8 9 10 11 12 13 | user=> (with-out-str (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))))\ " user=> |
那是我的信息;见到您很高兴;-)顺便说一句,该主题中给出的参考答案非常好阅读;因此,如果您有兴趣,不妨花些时间阅读它们。回到您的问题,尽管
1 2 3 4 5 6 7 8 9 10 | 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 |
挖一点...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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时,我了解到的一件事是,十分之九的内容是有用的。