Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Clojure more hot swappable than other JVM languages?

We can reload any function and/or variable in Clojure at runtime almost instantly. We can even change method signatures. The most we can do with Scala or Java is to use JRebel which is slow, commercial, and restricted. What is the difference that allows Clojure to be so interactive? Reading about this in Slack, I have found the following comments, but I wish to know more about it. Links to papers/articles clarifying the issue further is also appreciated (though not required).

It’s mostly because the language is set up to be reloadable. Clojure has a var indirection for every function or top level variable definition which you can mutate, so you can redefine just one function while keeping the rest of your environment the same and carry on

.

following up on that - there's indirection when the function name is in the code, but for a long running function that took another function as an argument (eg. you passed a handler function to an http server process startup) you can get the benefits of var indirection by hand - by passing #'handler instead of handler but otherwise you don't get the reloading (without restarting the process that took that arg)

.

kind of

direct linking replaces var calls being compiled with direct calls (edited) the var path however still exists and NEW code can still invoke via the vars

like image 290
HappyFace Avatar asked Apr 20 '18 13:04

HappyFace


1 Answers

The key to what you're asking lies on how Clojure identifies functions and runs them at runtime. First, Clojure functions are defined as vars, which is the Clojure name for their JVM root class, Var.

Clojure's runtime maintains a single ConcurrentHashMap called Namespaces. This map has Symbol keys (the namespace name) and Namespace values. Each Namespace in turn has an AtomicReference'd Clojure map (called "mappings") that is dynamically typed but which essentially has Clojure Symbol keys (the local variable name) and Var values.

When you invoke a Clojure function, it first looks up which namespace you're referencing in Namespaces and then looks up the specific variable in that namespace's mappings. This makes hot-loading code trivial - all you need to do is set a new <Symbol, Var> pair on a given namespace's mappings.

To go one level deeper, Clojure also maintains an awareness of "frames" (i.e. threads or additional bindings that might temporarily re-define variables within a local scope). These have their own ThreadLocal storage and a variable that is found in one of these will be used instead of the variable currently being stored in the namespace's mappings.


Clojure's approach here is possible because it doesn't try to store functions as JVM functions, but rather as Java Objects themselves that are kept in a map that can be rapidly accessed.

Clojure knows these Objects are in fact callable by checking to see if they satisfy a function interface (IFn). An object satisfies IFn by having an Invoke method. This is used for a wide number of pretty clever purposes, and explains why many of Clojure's core data structures (maps, vectors, keywords, etc.) are all also callable as functions.

like image 140
Venantius Avatar answered Sep 28 '22 15:09

Venantius