I actually really like JavaScript overall (lang + community + tooling, etc). But I always think of the language itself as a three legged puppy. The poor thing was disadvantaged since birth, but darn does it try hard.
— Paradoxquine, Clojurian
JavaScript is amazing. No other language can claim its level of ubiquity, running on browsers, servers, desktops, phones, and microcontrollers. Everyone seems to know and use JavaScript. Even so, many of us have experienced the dark side of JavaScript. We've been victims of its "bad parts", and I fear that I've spent more time avoiding problems than solving them.
Maybe that's because JavaScript is a Blub — a mediocre language that puts fundamental limits on problem-solving. The community, while loving the reach of JavaScript, seems to have reached the same consensus: JavaScript must be fixed. Thus: Coffescript, TypeScript, Dart, ES6/7, BabelJS, and all the other attempts to give JavaScript a fourth leg to stand upon.
But what if we don't need to fix JavaScript? What if, instead, we can supersede JavaScript with something better — a programming language designed to achieve a vision instead of mending a mistake?
With ClojureScript (Clojure compiled to JavaScript), that's exactly what we get: a dynamic, functional, pragmatic language that can not only improve our software, but more importantly, can improve our thinking.
When you're stuck with JavaScript in the browser, you not only write programs in JavaScript code, you also solve problems with a JavaScript mindset, a mindset limited by the language itself.
That makes the important advantages of ClojureScript hard to perceive, as the essential difference belongs in new patterns of problem solving rather than new syntax and grammar. These new thought patterns can only really be seen from the inside-out, by entertaining them in our own minds. We can only do that by actually using ClojureScript to solve a real problem, and afterwards, examining our thought processes.
So let's pick a problem and solve it with both JS and CLJS. Picking one out of the hat gives us, inevitably, FizzBuzz:
Write a program that prints the numbers from 1 to 100. But for multiples of three print
Fizz
instead of the number and for the multiples of five printBuzz
. For numbers which are multiples of both three and five printFizzBuzz
.
// JS: FizzBuzz
for (var i=1; i <= 20; i++)
{
if (i % 15 == 0)
console.log("FizzBuzz");
else if (i % 3 == 0)
console.log("Fizz");
else if (i % 5 == 0)
console.log("Buzz");
else
console.log(i);
}
;; ClojureScript: FizzBuzz
(doseq [n (range 1 101)]
(println
(condp = 0
(mod n 15) "FizzBuzz"
(mod n 3) "Fizz"
(mod n 5) "Buzz"
n)))
The JS and CLJS solutions don't differ dramatically in terms of logic, and we could spend a lot of time debating the merits of brevity, clarity, if
else
verses condp
, a discussion that Rich Hickey, BDFL of Clojure, sums up in his talk Simple Made Easy. But for our purposes, the forms of our language matter less than the kind of thoughts that such forms encourage.
Let's look at the code again, but this time going through the most likely pattern of thoughts that generate the solutions.
for
to iterate.%
in each iteration.switch
.switch
.if
else if
else
.doseq
and range
case
, cond
, or condp
.condp
, =
, 0
, and mod
.4. Given JavaScript, never ever use
switch
.
vs.
4. Given a pattern, identify abstractions.
Just at the step where the most important analytical effort occurs, JavaScript's deficiencies abort logical thought with axiomatic warnings: switch
statements should be avoided due to break
bugs. The JavaScript mindset suddenly shifts from problem-solving to damage-control, avoiding the language's "ugly parts". We compound this problem by doing what smart people do: we absorb this knowledge and begin to skip steps 3 and 4 entirely, and in doing so, skip thought patterns for building better abstractions.
ClojureScript does the opposite. Instead of seeking to avoid the danger of using one bug-prone function, it provides three types of switch statements, each suited to different kinds of control flow. These options not only allow one to engage in high-level forms of abstraction, but actually encourage this kind of thinking. Moreover, the entire pattern of thought begins at a much higher level, seeking to recognize patterns even before fully understanding the solution.
Having spent more time than any human being should allocate to FizzBuzz, and doing so with the mindset encouraged by ClojureScript, I find that condp
still doesn't fit exactly with the pattern of our solution. The functionality I'm looking for is, unsurprisingly, called pattern matching.
Pattern matching typically comes built into a language or not, as it relies on special syntax to be simple and clear. ClojureScript, like JavaScript, doesn't come with pattern matching. This would be a problem, except that ClojureScript comes with something better: macros.
(doseq [n (range 1 101)]
(println
(match [(mod n 3) (mod n 5)] ;; The pattern of remainders: n/3 and n/5
[0 0] "FizzBuzz" ;; If both remainders are 0
[0 _] "Fizz" ;; If the first remainder is 0
[_ 0] "Buzz" ;; If the second remainder is 0
:else n)))
Some languages run close to the "metal" — running insanely fast due to a language's close mapping to how computers actually operate. For certain discrete tasks that require maximum performance, such languages are vital.
Other languages take the opposite approach, running close to the "human metal" — our minds. The closer that a language can express (or in the case of macros, be modified to express) human thoughts, the closer we get to a program that we can understand natively. We no longer need to change our thoughts to fit the language. Instead, we change our language to fit the thoughts.
LISP is worth learning for a different reason — the profound enlightenment experience you will have when you finally get it. That experience will make you a better programmer for the rest of your days, even if you never actually use LISP itself a lot.
Eric Steven Raymond, How to Become a Hacker
This year is the year we are able to use Lisp all day every day, everywhere and anywhere. And among the many people we have to thank for that, we shouldn't forget the little browser-engine that could: JavaScript.
If you don't know much about ClojureScript, check out what the community is up to. From the core contributors to the newest beginner, I've found the Clojure culture to be exceptionally thoughtful, generous, and crazy-smart. I hope you find the same!