你能得到“代码即数据”吗? Clojure 中加载的函数?

发布于 2025-01-04 11:39:25 字数 345 浏览 1 评论 0原文

换句话说,“好吧,代码就是数据...

该线程解决了如何从源文件中读取的问题,但我想知道如何将已加载函数的 s 表达式获取到我可以读取和操作的数据结构中。

换句话说,如果我说,

(defn example [a b] (+ a b))

我不能在运行时获取该列表吗?这难道不是“代码即数据”的全部意义吗?

这确实是一个常见的 Lisp 问题,但我正在 Clojure 中寻找答案。

To put it another, way, "Okay, so code is data..."

That thread addresses how to read from a source file, but I'm wondering how to get the s-expression of an already-loaded function into a data structure that I can read and manipulate.

In other words, if I say,

(defn example [a b] (+ a b))

can't I get that list at runtime? Isn't this the whole point of "code as data"?

This is really a general Lisp question, but I'm looking for an answer in Clojure.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

十年不长 2025-01-11 11:39:25

您可以使用 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 函数。

在我看来,“代码即数据”指的是 lisp 的特性,其中源代码实际上是 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))))

: 行将 defn 形式中的 nth 替换为 my-nth

You can use the clojure.repl/source macro to get the source of a symbol:

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

But this is only part of the answer. AFAICT source looks up the source filename and line number that define the given symbol, and then prints the source code from the file. Therefore, source will not work on symbols that you do not have the source for, i.e. AOT-compiled clojure code.

Coming back to your original question, you can think of source as reading the meta data associated with the given symbol and simply printing that. I.e. it's cheating. It's not in any way returning "code as data" to you, where with code I mean a compiled clojure function.

In my mind "code as data" refers to the feature of lisps where source code is effectively a lisp data structure, and therefore it can be read by the lisp reader. That is, I can create a data structure that is valid lisp code, and eval that.

For example:

user=> (eval '(+ 1 1))
2

Here '(+ 1 1) is a literal list which gets read by the clojure reader and then evaluated as clojure code.

Update: Yehonathan Sharvit was asking in one of the comments if it's possible to modify the code for a function. The following snippet reads in the source for a function, modifies the resulting data structure, and finally evaluates the data structure resulting in a new function, my-nth, being defined:

(eval
 (let [src (read-string (str (source-fn 'clojure.core/nth) "\n"))]
   `(~(first src) my-nth ~@(nnext src))))

The syntax-quote line replaces nth with my-nth in the defn form.

日暮斜阳 2025-01-11 11:39:25

您可以使用 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=> 

You can get the source in recent versions of clojure with the source function.

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

to get the string as a value you can wrap this in 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=> 
久随 2025-01-11 11:39:25

这就是我的信息;很高兴见到你;-) 顺便说一句,该线程中给出的答案参考资料非常好读;因此,如果您有兴趣,您可能需要花时间阅读它们。回到你的问题,尽管 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 时学到的一件事是,十分之九查看源代码是有用的。

That was my message; nice to meet you ;-) BTW, the references given in that thread for answers were excellent reading; so if you're interested, you might want to take the time to read them. Back to your question though source seems to work for code that was loaded through a file, but it doesn't work in all cases. I think, specifically, it doesn't work for functions defined in the 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

Digging a little bit...

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

So yeah, it looks like it tries to load the source file off the classpath to try to spit it out for you. One thing I've learned when working with Clojure is that 9 times out of 10 it is useful to look at the source.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文