namespaces - 如何在 ClojureScript 中运行 eval 并访问调用 eval 的命名空间?

标签 namespaces eval clojurescript

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

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

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

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

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

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

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

是否可以?如果是这样,有没有人有一个简单的例子来完成它?

干杯

菲尔

最佳答案

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

首先,您必须确保库中的函数在 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 保存。)

Note: If we instead had the source for library.core compiled by the self-hosted compiler (by perhaps by using main.core/evaluate to eval "(require 'library.core)", along with properly defining a cljs.js/*load-fn* that could retrieve this source, things would be good, and the compiler analysis metadata would be in main.core/state. But this example is about calling ambient / pre-compiled functions in 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 的分析元数据。 ,它可以正确警告 arity 错误,例如
(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:
  • Klangmeister
  • power-turtle
  • life-demo
  • 关于namespaces - 如何在 ClojureScript 中运行 eval 并访问调用 eval 的命名空间?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51573858/

    相关文章:

    momentjs - 使用 cljsjs/moment 让 Moment 在 Clojurescript 中工作

    python - 如何在 python 代码中利用 locals()?

    c - C 中的 "struct namespace"是否有技术原因?

    ruby - 低效的 Ruby 方法命名 : passing namespace as argument as a way to call methods

    javascript - 为什么我们要用eval弹出一个新窗口?

    Python:我应该使用 eval、exec 还是 ..?

    c++ - 如何跨多个文件使用命名空间

    javascript - 如何将 Eval() 传递给 Javascript 函数

    clojurescript - OM 组件与普通函数

    clojure - 开发 "model"用于 clojurescript/clojure 应用程序