我们可以在运行时几乎立即重新加载Clojure中的任何函数和/或变量。我们甚至可以改变方法签名。我们用Scala或Java做的最多就是使用速度慢,商业化和受限制的JRebel。允许Clojure如此互动的有什么区别? 在Slack中读到这个,我发现了以下评论,但我希望了解更多。与论文/文章的链接进一步澄清问题也很受欢迎(尽管不是必需的)。
这主要是因为语言设置为可重新加载。 Clojure的 每个函数或顶级变量都有一个var间接 您可以变异的定义,因此您可以重新定义一个函数 同时保持环境的其余部分并继续
跟进那个 - 函数名称时的间接性 在代码中,但对于长期运行的功能,另一个 函数作为参数(例如,您将处理函数传递给http 服务器进程启动)你可以通过获得var间接的好处 手 - 传递#'处理程序而不是处理程序,否则你不会 获取重新加载(不重新启动采用该arg的进程)
有点
直接链接用直接调用替换正在编译的var调用 (编辑)然而var路径仍然存在,新代码仍然可以 通过vars调用
答案 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的核心数据结构(地图,向量,关键字等)都可以作为函数调用。