如何使函数对ClojureScript的eval可用?

3
在Dmitri Sotnikov的博客文章中(链接),提供了一个名为eval-str的函数,可以运行包含ClojureScript代码的字符串。
(defn eval-str [s]
  (eval (empty-state)
        (read-string s)
        {:eval       js-eval
         :source-map true
         :context    :expr}
        (fn [result] result)))

如果我有一些函数x,希望能够从eval字符串内部调用该函数,我该如何做?

1个回答

4
答案有两部分,假设 x 是与 ClojureScript 函数相关的变量:
  1. 编译器分析 x 的元数据需要存在于作为第一个参数传递给 cljs.js/eval 的状态中。这是因为在编译过程中,例如需要知道 x 的形参数量。
  2. x 相关的函数的 JavaScript 实现需要存在于 JavaScript 运行时中。(特别是如果该函数在 cljs.js/eval 调用期间实际上被调用,而不仅仅是被引用。)

如果 x 是一个核心函数(例如变量 #'cljs.core/map),则这两个条件都会自动满足。特别是当调用 cljs.js/empty-state 时将生成元数据(假设 :dump-coretrue),并且核心函数的实现已经加载到 JavaScript 运行时中。

但是,假设 x 是一个全新的函数,您希望它在自托管环境中进行编译。 “技巧” 是设置和重用编译器状态:例如将 (cljs.js.empty-state) 的结果放入变量中,并将其传递给每个 cljs.js/eval 调用。如果您这样做,而其中一个 cljs.js/eval 调用涉及编译 xdefn,那么编译器状态将被 修改(它实际上是一个原子),结果是编译器元数据将被放入状态中,当然,x 的 JavaScript 实现也将在 JavaScript 环境中设置(通过评估为 defn 生成的 JavaScript)。

如果另一方面,x是您的“环境”ClojureScript环境的一部分(例如,通过JVM ClojureScript编译器预编译,但仍可在JavaScript运行时中使用),那么您需要想办法将x的编译器分析元数据安排到传递给cljs.js/eval的状态中。如果您查看基于JVM的编译器的输出,您将看到包含此类元数据的<ns-name>.cache.json文件。查看这些文件中的数据,您可以确定其结构;有了这个,您就可以看到如何将所需信息交换到编译器状态下的[:cljs.analyzer/namespaces <ns-name>]cljs.js/load-analysis-cache!函数是为此用例而存在的帮助程序,一个自包含的示例位于https://dev59.com/L63la4cB1Zd3GeqPJj01#51575204

这似乎对于一个非常明显的使用要求来说过于复杂了。难道没有一种方法可以说“将调用eval的命名空间作为上下文传递给eval吗? - interstar
1
@interstar 您需要对正在评估的字符串使用的命名空间进行可用性设置(这可能与调用 cljs.js/eval 的命名空间不同)。虽然可能有一种自定义方法将其传递给 cljs.js/eval,但另一种方法是在执行此类代码之前评估 require(或 ns)表单(或直接使用 cljs.js/require)。如果所需的命名空间已经编译,则 cljs.js/*load-fn* 支持传回编译的 JavaScript 和相关分析缓存。 - Mike Fikes
@MikeFikes,你的建议将'(require ...)加入编译器状态非常好用。不确定这是否能通过高级编译,但会找出答案的。谢谢!这里有一个相对简单的示例可供需要的人使用: https://github.com/chr15m/speccy/blob/957f7cbcb8b51cfb3d92616df15a1384d15d3517/src/speccy/core.cljs#L35-L47(请注意第38行的require)。 - Chris McCormick
1
@ChrisMcCormick 很棒。顺便说一下,自托管的ClojureScript只与最高为:simple的优化兼容。 - Mike Fikes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接