为什么Clojure比其他JVM语言更热插拔?

时间:2018-04-20 13:48:14

标签: clojure jvm read-eval-print-loop jrebel hotswap

我们可以在运行时几乎立即重新加载Clojure中的任何函数和/或变量。我们甚至可以改变方法签名。我们用Scala或Java做的最多就是使用速度慢,商业化和受限制的JRebel。允许Clojure如此互动的有什么区别? 在Slack中读到这个,我发现了以下评论,但我希望了解更多。与论文/文章的链接进一步澄清问题也很受欢迎(尽管不是必需的)。

  

这主要是因为语言设置为可重新加载。 Clojure的   每个函数或顶级变量都有一个var间接   您可以变异的定义,因此您可以重新定义一个函数   同时保持环境的其余部分并继续

  

跟进那个 - 函数名称时的间接性   在代码中,但对于长期运行的功能,另一个   函数作为参数(例如,您将处理函数传递给http   服务器进程启动)你可以通过获得var间接的好处   手 - 传递#'处理程序而不是处理程序,否则你不会   获取重新加载(不重新启动采用该arg的进程)

  

有点

     

直接链接用直接调用替换正在编译的var调用   (编辑)然而var路径仍然存在,新代码仍然可以   通过vars调用

1 个答案:

答案 0 :(得分:10)

您要问的关键在于Clojure如何识别函数并在运行时运行它们。首先,Clojure函数定义为vars,它是其JVM根类Var的Clojure名称。

Clojure的运行时维护一个名为ConcurrentHashMap的{​​{1}}。此映射具有Namespaces个键(命名空间名称)和Symbol值。每个Namespace依次有Namespace'd Clojure AtomicReference(称为“映射”),它是动态类型但基本上有Clojure map键(本地变量名)和Symbol值。

当您调用Clojure函数时,它首先查找您在Var中引用的命名空间,然后在该命名空间的映射中查找特定变量。这使得热加载代码变得微不足道 - 您需要做的就是在给定命名空间的映射上设置一个新的Namespaces对。

为了更深层次,Clojure还保持对“框架”的意识(即可能暂时在本地范围内重新定义变量的线程或其他绑定)。它们有自己的<Symbol, Var>存储空间,并且将使用在其中一个存储器中找到的变量,而不是当前存储在命名空间映射中的变量。

Clojure的方法是可行的,因为它不会尝试将函数存储为JVM函数,而是将Java对象本身保存在可以快速访问的映射中。

Clojure知道这些对象实际上是可调用的,通过检查它们是否满足函数接口(ThreadLocal)。对象通过IFn方法满足IFn。这用于许多非常聪明的目的,并解释了为什么许多Clojure的核心数据结构(地图,向量,关键字等)都可以作为函数调用。