Warning: a very rambling article; less a solid proposal than me just exploring an idea that may lead to a dead end in a week or two after I’ve thought about it.
I really enjoy Clojure. Everything seems so well thought out and well designed; in a lot of ways it reminds me of Python, which is three ways to ironic because back in the day I started using Python because it was very Lispy. The cycle of life turns even on computer languages.
But unfortunately, I get the sense that there’s probably only room for one JVM language to make it big. As much as I like Clojure, I think that Scala will probably be the one that succeeds. It’s got the static typing, it’s got the Java-like performance, and it’s got a syntax that’s close-ish to Java, so it fills the niche that Java fills so well: a programming language that is ideal for large groups of developers.
However, it’s early enough in the development of Clojure that there’s the sense that maybe individual efforts could make a difference. (As a counterexample, Haskell is too far along — it seems like everything has been done already, or anything that needs to be done requires a deep knowledge of category theory and the latest functional design papers.)
Assuming that I would want to help out Clojure, what could be done?
Donate? Actually, I did that. I figured that I got as many hours of enjoyment out of Clojure as I did out of a typical game, so I contributed the price of a game.
Libraries? Well, I’ve written one or two. Will probably write more.
If Clojure is going to succeed, it needs a niche, an area in which it performs so well that it blows everything else away. Concurrent programming is one possibility, but there is a lot of competition there.
I have a modest proposal:
Clojure should be more like PHP.
Yes, yes, I know that programming in PHP is socially reprehensible, just one step above people that shop without their shirt while in Walmart and car salesmen.
But what if we were to give Clojure the trappings that make starting developers try it? Metaphorically, it’s like Gulliver’s Travels; it looks like a humorous, light-hearted adventure story, but underneath it’s biting political satire and deep thoughts. We lure developers in with the possibility of an easy, light-hearted way to write web sites… and then once they’re in there, once they’ve progressed from manipulating web sites and start to realize the benefits of advanced languages, they can deal with a sane language, built on top of the JVM and with the full power of a Lisp.
So call this Gulliver.
My impression, based on absolutely no citations or hard evidence, is that PHP became popular primarily because it is:
So, continuing the gedankenexperiment: if we were designing something that would be very approachable and very easy to use, what would it look like?
Compojure is an excellent base to start with, but it strays away from templates, and instead tries to do everything with compojure.html, a library that allows you to form everything with vectors and lists:
(html [:p "2 + 2 = " (+ 2 2)])Handy? Yes, if you’re a developer. But if a web designer is doing your HTML, or if you’re just starting out programming and you already know HTML, though, it seems an additional barrier to entry. So what if we turn it inside out and do it like PHP does it?
2 + 2 = <?= (+ 2 2) ?>
Trying something more complicated, in compojure.html this:
(html [:ul (map (fn [x] [:li x]) (range 10))])becomes:
<? (dotimes [x 10] ?>
<?= x ?>
<? ) ?>
A little bit longer, but treating it as HTML makes it conceptually easier for beginning programmers to understand, especially if you want to do something like changing the id or class with every row so your CSS can make it all pretty:
<? (dotimes [x 10] ?>
"><?= x ?>
<? ) ?>
Doing this is compojure.html is trickier:
(html [:ul (map (fn [x] [:li {:id (str "list-" x) :class (if (even? x) "even" "odd")} x]) (range 10))])So, let’s lure people in with easy to use, and they can pick up vectors and S-exprs and notational convenience later. Converting a template file into the basic code is easy:
(defn- transform-string "Internal. Transform a string into the appropriate echo statement. An empty string translates into a single space." [s] (if (> (.length s) 0) (format "(gulliver/echo %s)" (pr-str s)) " ")) (defn- transform-code "Internal. Transform code so that it can run as a render routine." [s] (if (= (first s) \=) (format "(gulliver/echo %s)" (.substring s 1)) s)) (defn- convert* "Internal. Given a string that represents a Clojure template, produce a flat list of the statements necessary to produce that template." [s] (let [m (re-matcher #"(?s)(.*?)<\?(.+?)\?>|(.+?)$" s)] (loop [next (re-find m) buffer ["'("]] (cond (not (nil? (nth next 3))) (eval (read-string (apply str (conj buffer (transform-string (nth next 3)) ")" )))) :otherwise (recur (re-find m) (conj buffer (transform-string (nth next 1)) (transform-code (nth next 2)))) ))))Resulting in…
==> (convert* " (dotimes [x 10] ?>= x ?> ) ?>") ((gulliver/echo "") (dotimes [x 10] (gulliver/echo "") (gulliver/echo x) (gulliver/echo "")) (gulliver/echo ""))Once you have the basic code generation, it’s easy enough to hook it up to a default servlet that automatically invokes it when necessary. There’s a fair bit of handwaving going on here — regexes are questionable for this, there are some interesting issues with namespaces, and it would require some method of resolving dependencies between files, but heck, it’s only 100 lines of code; it was really quick to put together and gives us a sense of whether this can work out.
So, with the source on github, your basic server becomes…
(use 'gulliver) (defserver web-server {:port 8080} "/*" (servlet gulliver/template-servlet))Easy! As an test, an example file:
(import 'java.util.Date) ?> Delivered via Gulliver Expressions at work: = (+ 2 2) ?> Looping: (dotimes [x 10] ?> = x ?> ) ?> Here's a date: = (Date.) ?>… does the right thing. How about a time test?
Starting time test... (let [ start (System/currentTimeMillis) ] ?> Adding 1..100000: = (reduce + 0 (range 1 1000001)) ?> (took = (- (System/currentTimeMillis) start) ?> ms) ) ?>… runs in about 50ms. PHP takes about 130ms on the same sequence on my laptop, so, while there are tons of optimizations we could do (like not doing a stat on the file every time the page is rendered (!)), it nonetheless performs well enough that maybe this is a viable option. Finally, as a special bonus, a stupid eval trick that evaluates Clojure code:
(when ((request :params) :code) ?> = (str (eval (read-string ((request :params) :code)))) ?><? ) ?>
Take that, non-dynamic languages!
.
Next part: Schema-ing Against MySQL
Comments are moderated whenever I remember that I have a blog.