The Evolution of Lisp', and thinking about Post Scarcity Computing and about Clojure.
Lisp has failed to get traction too many times because people insisted on their own idea of what constituted a pure Lisp. Let's be clear about it, if I were designing my perfect Lisp it would be different from Clojure in a number of significant ways. Nevertheless, Clojure is more or less the best Lisp we have now, and furthermore it has traction. Furthermore, it has a number of most excellent features which, had I not been exposed to Clojure, I would not have thought of myself. So if you're going to build a post-scarcity computing environment now, Clojure is not a bad place to start.
But let's think about how Clojure impacts on the ideas I put forward in Post Scarcity Computing. Firstly and most importantly, in Clojure data is (with some special case exceptions which we'll come to) immutable. That has a number of interesting consequences, and one of them is that an older data cannot ever point to newer data.
How does that impact on a multi-user environment? Well, probably no-one's ever thought about that in the context of Clojure, because probably no-one's ever thought about having several people connected into the same Clojure session. But, suppose you connect to a base Clojure environment, and run some computation, and then I connect to the same base environment. I cannot see anything that's going on in your computation, because all the partial results of your computation are newer than the base environment.
You can try this yourself. There's probably simpler ways to do this, but here's mine: first, install emacs 24, and cider. Next, open three terminals, and run
lein repl :headless
in one of them. This starts a headless nrepl, and prints out the port number it's running on:
simon@fletcher:~$ lein repl :headless
nREPL server started on port 48705 on host 127.0.0.1
In the other two, start headless emacs sessions:
simon@fletcher:~$ emacs -nw
In each emacs session, type
You'll be prompted for a host, with a default of 127.0.0.1 (localhost). Accept this. You're then prompted for a port; type in the port number that the nrepl server printed.
Now, you have two terminals both connected to the same Clojure session. In one, evaluate something:
user> (list 'a 'b)
In the other, type *1 to look at the result of the last evaluation:
But in the first window, the one where you evaluated something, *1 returns the value you'd expect:
So, as I said above, two separate sessions whose computations and local environment are invisible to one another. How can they be made visible? It's as easy as defining a var:
user> (def shared (list 'a 'b))
Now, in your other emacs terminal, the value of shared is what was set in the first:
So, by default, everything a user does is private. It can be made public by simply defining a variable, in the default (user) package. Again in one terminal:
user> (ns foo (:use clojure.core))
foo> (def hidden (list 'p 'q))
In the other:
user> (ns bar (:use clojure.core))
CompilerException java.lang.RuntimeException: Unable to resolve symbol: hidden \
in this context, compiling:(/tmp/form-init3330134955068707486.clj:1:691)
Unless the other user can guess the package name you're working in, they can't see the variables you bind. Of course, in Clojure as it exists now, if you do know the name of the package another user is using, you can see their variable bindings, because Clojure as it exists now isn't designed as a multi-user environment. And security by obscurity isn't really security at all.
But nevertheless you can see that Clojure could easily be made an effective multi-user environment. You can see that it would be easy to write an 'executive' function, which provided its own read-eval-print loop in an environment in which it had a (mutable) Java HashMap bound to a local variable, and could intercept a particular input pattern to store 'user-private' variables into that hashmap.
It's not hard, in fact, to see how a multi-user system could be built on a persistent Clojure session. I'll return to this soon, in further blog posts.