Javascript is not Scheme
Nor is it Logo. Logo also isn’t Scheme.
A prefatory note: This is kind of a weird post; I don’t really know who its audience is. It explains, as though to a general audience, some facts about programming and programming languages. But it also uses code examples and programming knowledge liberally throughout, in a way that might only make sense if the reader already knows how to code, feels comfortable reading code in multiple languages, and knows some of the common lore of programming culture. I’ve tried to make it accessible to non-programmers while avoiding boring folks who code. I’m still figuring this out. I beg your patience.
To the chagrin of many programmers worried about purity (and also concerned with protecting their ivory towers), Javascript has been asked to do just about everything, and has pretty much done it. No longer only used to orchestrate rollover images, JS has more or less eaten the world (thanks in large part to Node’s liberation of JS from the browser). npm, the open-source software repository for JS, is the largest software package manager in the world. npm hosts, at current, more than 650,000 software packages, and serves more than 5 billion downloads each week. Meanwhile, JS gets a lot of hate as a shitty language. It has warts, many of which stem from the conditions of its inception: Brendan Eich famously invented the thing in 10 days at Netscape, where he was hired to write “Scheme in the browser.” But then make it Java.
Javascript, however, is not Scheme.
Here’s one (not terribly novel) way to think about JS: it’s a weird combination of Scheme’s version of Lispish functional programming and a C-like syntax that encourages imperative programming. (Which is to say nothing about its equally weird deployment of Self-style prototypical inheritance for its object-oriented bits, in place of Java’s classical inheritance.) JS is stupid Lisp in Java’s ill-fitting clothing. It’s Alonzo Church going to a Halloween party as Alan Turing. It’s the lambda calculus in Turing-machine drag. It’s a quirky nerd with a cynical marketing department. (The syntax and the name of the language — JavaScript—were determined by Netscape’s desire to ride the coattails of Java’s popularity in the early 1990s.) It’s multiparadigm salad.
JS, then, is a whole lot like English. It’s become a lingua franca of programming: almost every programmer knows enough to hack something together, often in ways that aren’t idiomatic, but get the idea across well enough. JS hosts a huge number of dialects and pidgins and regionalisms. Etymologically, it’s the merger of two different languages from different language families — JS: Lisp and C; English: Anglo Saxon and middle French — both of which have been pretty radically simplified into a beast of a language with a ton of irregularities that’s nevertheless, relatively speaking, really easy to learn for all its weirdness. It’s a very forgiving language — and therefore really easy to abuse.
One of the more important moving parts of my ongoing Thinking with Computers project is to translate as much of Logo’s programming environment, ideals, and mechanics forward into JS as possible, to make it available to folks learning and working today, in today’s programming environments. This is a form of historical research, understanding more about Logo, what it meant, and what it might mean now, by shifting its context and learning its tech. It’s also an interesting thing to do to learn about JS, by some measures the most widely used programming language in the world.
Now, of course, JS can’t simply become Logo; they’re very different creatures. But it is worth pointing out that in Scheme, Logo and Lisp share a common ancestor. Logo is Scheme for kids; JS is stupid Scheme with a hoarding problem. What I propose to do in this post is step through some of the similarities and differences between the three languages using a dead-simple definition of a factorial function. In particular, my goal is to see if we can’t abuse JS to turn it into something like Scheme: the impedence mismatch itself holds lessons. Now, it will requires a fair bit of abuse to get it there. It will begin to feel like it might not really be JS anymore. What will emerge is a small, weird subset of JS that will, for its weirdness, be perfectly fine for learners (since they won’t know any different) and may well appear to be a totally different beast for folks who learn(ed) JS the mainstream way. This is really a first entry in thinking about what an idiolect of JS grounded in Logo’s hippie idealism and emphasizing the inheritance of Scheme’s heady intellectualism might look like, and why you might want it to look that way.
Scheme is a Lisp. Lisps are among the simplest of programming languages, first invented in the late 1950s. Scheme is a dialect of Lisp best known for being the introductory language to MIT’s computer science curriculum in the 1970s. It’s the language of the monumental Structure and Interpretation of Computer Programs, the most famous computer science textbook. It is the most minimalist of Lisps, already minimalist languages. (Other Lisps include Clojure and Common Lisp and Racket.) What that means, in brief, is that it’s got lots of parentheses. It’s rather a bit esoteric, a bygone language beloved by the most intellectual of programmers and ignored by people who focus on productivity.
Scheme is also a prototype for both JS and Logo. But both of these, for different but related reasons — essentially, making adoption easier for people who you might imagine having difficulty with Lisp: children and web developers — depart from what what makes Scheme Scheme, what makes a Lisp a Lisp: its distinctive, minimalist syntax. Understanding something about that syntax will help us think about Logo and JS. I don’t want to get bogged down into details, but we’ll need a few: the way Scheme and other Lisps organize their code is, simply as lists (called S-expressions), which are demarcated by parentheses. For example,
(display (list 'foo 1 true)) ;; prints (foo 1 #t) to the screen
In this case, list
is creating a list of all the arguments passed to it — 'foo
, 1
, and true
, since lists can hold any combination of types (in this case, a string, number, and boolean, respectively)—which is then passed to display
as an argument, which does pretty much what it says on the tin. Each expression is bounded by parentheses, and these expressions can nest. (S-expressions can, and very frequently do, contain other S-expressions.)
For a slightly more illuminating example, we might implement a totally naive recursive implementation of factorial
alongside two rather different and equally naive versions in JS. Don’t worry, you don’t really need to understand the code line-by-line:
Let’s notice a few things:
- In Scheme, the function definition and invocation have nearly the same basic form. Technically,
define
is a “special form” (as iscond
), but for our purposes here that’s not really an issue; that means a semantic, not a syntactic, difference. The definition of a function effectively looks like a call to a function. And the conditional constructcond
is also syntactically identical to a function invocation. The test for equality is a normal function, and looks identical in its invocation, with the exception that the function name is a symbol instead of a word:(= n 1)
. Basically, there are only S-expressions that are all syntactically identical. The complexity of the behavior comes from combining them by nesting them in interesting ways. But the nesting is deep! Thedefine
expression ends with six (!!!!!!) close parens. - In the ES6* version, the code is a lot shorter. It’s a single line! But it’s a long line full of punctuation. This is a fairly common coding style in JS these days, and it reads really nicely if you know what you’re looking at. (It is also the style in which I am most comfortable.) But you’ve got all these glyphs (
=>
,===
,?
,:
) doing basically all of the work: function definition, argument-passing, testing for equality, and conditionally returning a particular value. For parentheses, we have substituted a whole gobbledigook of ASCII. - In the ES5* version, we still have some strange punctuation (two forms of brackets:
()
and{}
; JS also uses[]
). But more than anything else, we have language keywords —function
,if
,else
,return
—that don’t work like user-defined functions. This might be easier to read since it’s closer to English (maybe) but it also means developing different mental models for the different “parts of speech” of the language (and which brackets belong to them). This version reasonably closely resembles function definitions in languages like Java and C.
- Those two JS versions look almost nothing alike, but they’re part of the same language and do (almost) exactly the same thing. There are no fewer than five ways to define a function in JS, and they’re not all semantically equivalent. It’s way too much to handle for students early in coding. Hell, it’s enough that people are complaining that the language is becoming too big for professional developers and can we please stop adding things to the language already. One of the most important questions a teacher of JS or almost any other mainstream programming language must ask is: what subset of the language will we use? Why? (Often the answer is: the stuff that looks most like Java and C, because that the part of the language the teacher learned first, and so has a mental model, if only an implicit one, of how to teach and learn it.)
(* It’s a weird story of terminology and copyrights and trademarks and technical committees and browser vendors and whatnot, but the short version is that in 2015 a radical revision to JS was finalized as ES6. ES5, naturally, was the previous version, and didn’t have things like the arrow punctuation mark (=>
) to define functions concisely.)
There are few consequences here: there may be a lot of parentheses in Scheme, and balancing nested parentheses is a pain. But for all that, Scheme is far simpler than either of the JS versions. Scheme famously provides an almost blank canvas: you can do anything, and do it elegantly, but you have to invent all the things you need to get you where you want to go. Inventing the things, though, teaches you a lot of really important things. If only the fact that you can build complex and robust systems by putting small, simple pieces together carefully and intelligently.
More important for my purposes here, though, lies in the fact that there is no syntactic difference between what the user defines —in this case, factorial
—and what the language provides — cond
and define
. As Gary Steele has put it (p. 6, although if you have time, you really should watch his talk, it’s a lovely performance):
In Lisp, new words defined by the user look like primitives and, what is more, all primitives look like words defined by the user! In other words, if a user has good taste in defining new words, what comes out is a larger language that has no seams. The designer in charge can grow the language with close to no work on his part.
In JS, whether it’s keywords or punctuation, the language has a bunch of stuff that belongs to it. In Scheme, effectively, almost everything belongs to you, or at least it seems like it does. It’s all just lists (S-expressions are just lists). This is important for one of Lisp’s more notorious features, macros, which let programmers modify the language as they go. Because the code is just lists, and lists are the basic data structure Lisp was devised to process and modify — “Lisp” is a contraction of List Processor — Lisp code can modify itself in ways many other languages, including JS, cannot. Since each programmer can, in effect, invent private languages, Lisps can fragment quickly into dialects and idiolects that may be mutually unintelligible or incompatible. If today’s most popular Lisp, Clojure, hasn’t fragmented, that’s largely because it has established a strong norm of avoiding macros, and entrusted its coherence to a strong man, Rich Hickey, who is its “Benevolent Dictator for Life.” By contrast, JS is populist and noisy and rough-and-tumble and full of brilliant and bad ideas. No cathedrals in JS, only the bazaar.
In Scheme, there is profound continuity between the forms the programmer defines and the forms the language proscribes. Folks who taught Scheme — including Abelson and Sussman in The Structure and Interpretation of Computer Programs, as well as Brian Harvey — tend to say things like: you never really have to teach the language; students just pick it up. That may not really be true, or the population taking intro to computer science classes at MIT and UC Berkeley so self-selecting that if it’s true it’s also misleading — but you can’t really say that about Python (the replacement for Scheme at both MIT and UCB), even if its syntax is pretty easy as far as these things go. And you most certainly cannot say that about JS, whatever great good work the p5.js or WoofJS folks are doing.
All that said, we can begin to turn JS into something like Scheme. Let’s use Logo as a waypoint on our path. When Seymour Papert was devising Logo in the 1960s, he was working with Lisp (pre-Scheme): Logo was explicitly imagined and devised as Lisp for kids. So let’s take a look at that same factorial
function defined in Logo:
Logo’s biggest departure from Lisp (it’s really only Lispish) is that it has no parentheses. You invoke a function by writing the name of a function and then putting its arguments after it, without adornment: factorial 6
or forward 100
. This has the effect of making the entry to Logo, when you’re moving a turtle around on a screen directly and imperatively, much easier. (When I’m working with Scheme, meanwhile, the parentheses still cause me headaches, even when I’m using parinfer to outsource parenthesis handling to the code editor.) Logo keeps track of the number of arguments a function takes, but that means it needs ways of bundling some lists together: for that it uses the square brackets you see enveloping factorial
’s return values — which in Logo are identically calls to output
. It also has keywords that aren’t just special forms: to
and end
are doing special things there, and the punctuation of the parameter :n
is also slightly cryptic. That said, ifelse
, like Scheme’s cond
, is effectively a function invocation. But because the parentheses are missing, it might be hard to see how the following two lines of code are still part of its invocation.
Logo isn’t quite as beautifully minimalist as Scheme (because, not despite, its missing parentheses, since now we have to worry about when to use what). Despite that, it has a proven track record for teaching children programming concepts, how might we do some of this in JS? Consider the following (and if you’re not interested in me showing my work, skip to the bottom where we see a third version of a factorial
in JS):
I’ve shown my work in the JS snippet above; it works and it’s actually not very complicated to get working. In a learning environment, those functions (cond
and equals
and always
and many more) would be provided as a standard library for that environment, and would be taught as a way, perhaps the way, of handling conditional logic. They would feel like they’re part of they language you’re learning; in fact, they would just be part of the language. In this version, in this Lispish JS, what we have is conceptually closest to the Scheme example (which makes sense, since cond
is modeled off Scheme), and has punctuation that resembles Logo’s (to be sure, with a bit more overhead). It’s closer to Logo and Scheme than it is to the earlier JS examples. It’s got way less punctuation density than the ES6 one-liner in JS, and way less weird keyword noise and ceremonial overhead than the ES5 version. It also encourages some good habits of mind in thinking about conditional logic.
To be sure, I am not suggesting that this is a superior way to implement conditional logic in JS, since precisely nobody (to a large number of significant digits) does it this way. (That said, it’s not so far from JS’s native switch
operator, and it’s only a hop, skip, and a jump away from the wonderful pattern matching in a language like Elixir. There’s a proposal to add pattern matching to JS, and some hacks to get close to it now.) But: if you’re learning this idiolect of JS, the language you’re learning would feel, and indeed would be, very different indeed from what you pick up from Khan Academy and the “everybody should learn to code” cottage industry. It would avoid loops and embrace lambdas. I honestly think that difference is something to be embraced — even if I don’t think that a stubbornly functional approach to JS.
I suspect that folks who learned to code in imperative languages with C-like syntax would feel that this approach is rather harder to learn. In part, that’s because this version is functional in its approach, rather than imperative — a distinction that, if you don’t know those terms already, matters mostly because functional programming (FP) has a reputation for being weird and abstract and hard. That, in turn, is in no small part because it’s not familiar to those whose programming education has been mainstream. (That said, the jargon of FP is indeed extremely abstract. And some of FP really is hard to grok. But you really don’t need to know about monoids or bifunctors to do real functional programming, anymore than you need to understand the Gang of Four patterns to write object-oriented code — especially on the dynamically typed Lispy side of things, which is where we are here. Haskell is not an inviting language.) Papert showed that kids could learn the Logo version of the thing very well indeed. The similarity between the Logo version and the functional JS one is marked. Put otherwise: functional programming is for kids! Really. Or at least for language learners. And yes, it’s true: this funny functional cond
construct is probably drastically slower than its native counterparts (I haven’t tested it, but I have my hunches), and does have a bit higher conceptual overhead than its Lisp counterparts. But speed doesn’t matter all that much when you’re coding for fun and concepts, and not for profit. Meanwhile, I’m not sure what the conceptual frames would have to look like here — clearly, on this model, lambdas would have to come very early indeed. But that’s not the point, not yet.
I should say, I’m also not (yet) arguing that this is the right thing pedagogically. It might be! But it very well may not. Right now the questions are, instead: What can you do in JS? How can you shift your thinking around in its domain? What can a JS that does not look or feel like JS look and feel like?
Allow me to close with a coda akin to bikeshedding about functional programming in JS:
One of the more pronounced cultural facts of the contemporary JS landscape, at least to my eye, is that folks programming in functional idioms in JS more often than not take a certain class of contemporary functional languages as their standard for FP. I’m no different, really: I want JS to be more like Elixir, which is a total joy to write. There’s a real desire for new syntax and accompanying semantics: people want new punctuation. But the punctuation space is getting saturated (e.g.). Even still, I want a number of the proposed changes to the language standard to go through. (My ideal JS has the pipeline operator, partial application, do expressions, protocols, function decorators, and pattern matching, all of which are in the TC-39 proposal pipeline for additions to the language. Please don’t get me started about tail calls, which would be a real step towards making JS more like Logo and Scheme.) Relatedly, folks working on hardcore functional programming in JS tend to fixate on some of the stylistic and conceptual idioms of statically-typed, purer FP languages like Haskell (for example, in the Sanctuary library, or even Ramda’s predilection for a tacit [a.k.a. pointfree] style). Monads abound.
To the extent that a programming environment for language learners must be damn stable, and good about absorbing the weird shit new coders throw while offering reasonably helpful error messages, those tools are actually really helpful, if less Lispy than they might be. They help put boundaries on JS’s fairly radical permissiveness. You’re already working against the language in encouraging a nothing-but-FP style, and ensuring stricter code can help mitigate some of the difficulties that come with this approach. People devising resources for functional programming in JS have recently begun acknowledging that too much strictness itself works against the language. (For example, the wonderful Folktale library offers less strictness but much nicer play than Sanctuary.)
My point, though, is this: as much as I want new syntax (and semantics) to make certain things easy and to put the language on my side when I write functional code, perhaps the right ethos is to remember that JS is Scheme. Put pressure on the language, squint, teach people weird idiolectic versions of the language. Do not hope so eagerly for JS to become F# or Haskell or Scala or OCaml or Elixir. Write tools to support functional programming in styles that feel more like Scheme than like Haskell. Shift the things that feel like they belong to the language: JS’s functional aspects mean that you can do, with very little overhead or head-banging, some very interesting things that might begin to feel like they’re part of the language. How far might we take that? What tools might we imagine? How might we help people learn JS differently, accessibly, and from the ground up?