【问题标题】:How can I run eval in ClojureScript with access to the namespace that is calling the eval?如何在 ClojureScript 中运行 eval 并访问调用 eval 的命名空间?
【发布时间】:2018-07-28 17:52:35
【问题描述】:

我有一个函数库,我想让用户在浏览器中使用它。

所以我想设置一个这样的情况:

我正在使用 figwheel 和 devcards 进行开发。

在主 core.cljs 中,我 require 库中的各种函数,所以它们都在范围内。

现在我想让用户输入一些调用该库的代码。

我知道如何使用 eval 运行该代码,但我看不到如何使我的库函数对正在评估的代码可见。

我对我看到的大部分文档感到困惑(例如How can I make functions available to ClojureScript's eval?

有可能吗?如果是这样,有人有一个简单的例子吗?

干杯

菲尔

【问题讨论】:

    标签: namespaces eval clojurescript


    【解决方案1】:

    是的,可以提供对评估代码使用的环境/预编译库的访问。

    首先,您必须确保库中的函数在 JavaScript 运行时中可用。换句话说,避免:advanced 优化,因为这将消除在编译时(DCE)未调用的函数。自托管 ClojureScript 与 :simple 兼容。

    其次,您需要将分析元数据提供给将在浏览器中运行的自托管编译器(使用cljs.js/load-analysis-cache!cljs.js/empty-state 的可选参数)。

    说明如何执行此操作的最小项目如下(以及https://github.com/mfikes/ambient):

    项目代码

    src/main/core.cljs:

    (ns main.core
      (:require-macros [main.core :refer [analyzer-state]])
      (:require [cljs.js]
                [library.core]))
    
    (def state (cljs.js/empty-state))
    
    (defn evaluate [source cb]
      (cljs.js/eval-str state source nil {:eval cljs.js/js-eval :context :expr} cb))
    
    (defn load-library-analysis-cache! []
      (cljs.js/load-analysis-cache! state 'library.core (analyzer-state 'library.core))
      nil)
    

    src/main.core.clj:

    (ns main.core
      (:require [cljs.env :as env]))
    
    (defmacro analyzer-state [[_ ns-sym]]
      `'~(get-in @env/*compiler* [:cljs.analyzer/namespaces ns-sym]))
    

    src/library/core.cljs:

    (ns library.core)
    
    (defn my-inc [x]
      (inc x))
    

    用法

    我们有一个main.core 命名空间,它提供了一个evaluate 函数,这个示例将展示如何在环境/预编译的library.core 命名空间中调用函数。

    首先,通过浏览器REPL启动

    clj -m cljs.main
    

    在 REPL 中,通过评估加载我们的主命名空间

    (require 'main.core)
    

    测试我们可以评估一些代码:

    (main.core/evaluate "(+ 2 3)" prn)
    

    这应该打印出来

    {:ns cljs.user, :value 5}
    

    现在,由于main.core 需要library.core,我们可以调用该命名空间中的函数。在 REPL 上对此进行评估将产生 11:

    (library.core/my-inc 10)
    

    现在,让我们尝试使用自托管 ClojureScript 中的“环境”函数:

    (main.core/evaluate "(library.core/my-inc 10)" prn)
    

    您将看到以下内容

    WARNING: No such namespace: library.core, could not locate library/core.cljs, library/core.cljc, or JavaScript source providing "library.core" at line 1
    WARNING: Use of undeclared Var library.core/my-inc at line 1
    {:ns cljs.user, :value 11}
    

    简而言之,即使 library.core.my_inc 在 JavaScript 环境中可用,并且确实可以调用,产生正确的答案,你会收到自托管编译器的警告,它对此一无所知命名空间。

    这是因为编译器分析元数据不在main.core/state 原子中。 (自托管编译器有自己的分析状态,保存在 JavaScript 环境中的原子中,与 JVM 编译器分析状态分开,通过 Java 环境中的 Clojure 保存。)

    注意:如果我们使用自托管编译器编译的library.core 的源代码(可能通过使用main.core/evaluate 来评估"(require 'library.core)",以及正确定义一个可以检索此源代码的cljs.js/*load-fn*,事情会很好,编译器分析元数据main.core/state. 但这个例子是关于在library.core 中调用环境/预编译函数。

    我们可以通过使用cljs.js/load-analysis-cache! 加载与library.core 命名空间关联的分析缓存来解决此问题。

    此示例代码通过使用从基于 JVM 的编译器中获取分析缓存的宏直接将此分析缓存嵌入代码中。您可以通过任何您想要的机制将此分析缓存传输到浏览器;这只是说明了一种简单地将其直接嵌入到运输代码中的方法(它只是数据)。

    继续评估以下内容,看看该命名空间的分析缓存是什么样的:

    (main.core/analyzer-state 'library.core)
    

    如果你打电话

    (main.core/load-library-analysis-cache!)
    

    将加载此分析缓存以供自托管编译器使用。

    现在如果你评估

    (main.core/evaluate "(library.core/my-inc 10)" prn)
    

    您不会看到任何警告,这将被打印出来:

    {:ns cljs.user, :value 11}
    

    此外,由于自托管编译器现在具有libraray.core 的分析元数据,因此它可以正确警告一些错误,例如

    (main.core/evaluate "(library.core/my-inc 10 12)" prn)
    

    将导致打印:

    WARNING: Wrong number of args (2) passed to library.core/my-inc at line 1
    

    上面说明了当您没有为命名空间提供分析器缓存时会发生什么,以及如何使用cljs.js/load-analysis-cache! 修复它。如果你知道你总是想在启动时加载缓存,你可以简单地使用 cljs.js/empty-state 的可选参数在初始化时加载这个缓存:

    (defn init-state [state]
      (assoc-in state [:cljs.analyzer/namespaces 'library.core]
        (analyzer-state 'library.core)))
    
    (def state (cljs.js/empty-state init-state))
    

    其他项目

    一些(更详细的)项目使库函数可用于浏览器中的自托管 ClojureScript:

    【讨论】:

    • 添加了一个指向最小项目的链接,显示如何从自托管的 ClojureScript 调用环境函数(说明如何加载分析缓存)。
    • 非常感谢这位迈克。我可以看到这需要一些学习,但看起来我可能能够很好地理解它以使用它。我可能会带着更多问题回来 ;-) 但再次感谢。
    • @interstar 我已经更新了示例以避免使用 slurp 的复杂性;相反,它直接从 JVM 编译器的状态中获取分析缓存。
    • 对我来说效果很好。我将初始化状态放入的命名空间与我的“环境”函数所在的命名空间相同。“环境”函数必须在调用初始化状态之前出现,否则分析器状态不会拾取变量.
    • @pankajdoharey 这似乎是一个不相关的问题。 (只是想看看如何在这个特定的 SO 的上下文中回答这个问题。)但是,要回答您的问题,在自托管的 ClojureScript 中,您可以以任何您认为合适的方式定义 cljs.js/*load-fn*。如果这样做,您应该遵守该变量的文档字符串中描述的顺序。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-26
    • 1970-01-01
    • 1970-01-01
    • 2017-07-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多