JavaScript Isn't Scheme (2013)
journal.stuffwithstuff.com
journal.stuffwithstuff.com
* I'd be curious to hear if @munificent has changed his mind about this article at all since it was written.
Since 2013, JS has added optional lexical scoping and continuations (promises). There is arguably a distaste for mutation in the community and among the major JS frameworks, not sure if that counts.
(but oh gods 'let' makes me so happy, having to get sensible closure behaviour by typing out let-over-lambda shenanigans by hand was -ing horrible)
That's fair, agreed, and I don't mean to imply equality. The argument was that JS doesn't have continuations at all though, which wasn't completely true in 2013, and is trending less true over time with promises and generators, right?
Some might argue that a function which is so long that it needs block scoping, whether for visibility or storage lifetime management, is too long.
I guess it’s at least “mostly harmless”, though :-)
(“class” arguably sucks in ES2015)
for (let x in list) {
callbacks.push(function () { x+3 });
}
instead of: for (var x in list) {
callbacks.push((function (x) {
function () { x + 3 }
})(x);
}
is a huge advantage to me. (yes, I know arrow function notation exists, but given I was explaining "why I love let" it would've been unfair to use it in the "good" example)OTOH:
list.forEach( function( x ) {
callbacks.push( function () { return x + 3 } )
}
I guess I tend to use forEach, or one of the Ramada.js operations, rather than for loops these days.EcmaScript isn’t Scheme, or Smalltalk/Self, or Ruby. But it sucks a lot less than the other language I’m allowed to use at work :-)
(I pretend that ES is mostly Ruby)
Furthermore, there is already a compiler that implements first class delimited continuations in JavaScript: http://stopify.org
The project implements the control operator (which can implement other operators like call/cc). The research paper linked on the website talks about how control is implemented.
I don't think of promises as being particularly close to continuations, not even one-shot continuations. I think it's more "If you use promises you have to manually turn your code into continuations." :)
I wrote about my thoughts on that idea here: http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y...
One thing that has changed in the past few years is I don't see people comparing JS to Scheme very much anymore. I don't know if it's because JS got big enough with ES6 that the comparison seems forced, or because Scheme doesn't have the cachet it had a few years ago, or what.
Today, people who like JS seem to like it because they like JS — which usually means they like some combination of dynamic typing, JS's syntax, the platforms where JS runs, and the frameworks people built for it. I think those are all great reasons to prefer JS, much better than comparing it to some other language that it may or may not be similar to.
(There are also a lot of people who like JS today because it's the only language they know. I don't think that's a super informed choice, but everyone goes through a first love in their life, and the object of that affection doesn't say much more about its subject than the moment in time that they happened to start programming. My first love was Apple BASIC, but I think I turned out OK.)
FutureBASIC was my jam. QuickBASIC was too slow so I tried to learn C, but it was brutal trying to learn that on my own. This was before the web and I knew zero people who knew any C. Something like a misplaced semicolon would get me stuck for weeks.
But FutureBASIC was almost as easy as QuickBASIC, fast enough for games, gave full access to the Macintosh Toolbox (graphics), and had a really cool visual interface builder (PG PRO). I could make software in it that looked like "real" Mac applications (which felt amazingly legitimizing to teenage me) and ran fast enough for real games.
It was really hard to make the switch to C because FutureBASIC was good enough for quite a long time. I wouldn't be where I am today without all the fun I had using it back then.
In this respect JS is better. BASIC had the unique talent that although it was constrained like a "scripting" language, it didn't have the easy-to use high-level constructs. JS also was very minimal to start out with, but (a) you got something in return (the ability to run in a browser), and it was powerful in a few interesting ways (closures, and for good or ill, dynamic typing).
> Imagine being a construction worker surrounded by big burly dudes, arm hair fluttering in the winds of their swinging hammers. And you’re there pushing in nails using this ragged spit-stained blankey you’ve had since you were a kid. It’s embarrassing, despite the fact that your blanket does actually get those nails in. Somehow.
This actually made me laugh out loud (And i'm a full time JavaScript developer).
> You feel insecure, a bit of a weakling. You’re a Belieber at a Meshuggah show and what you could really use is some street cred.
If there was interest in using Smalltalk, it makes a lot of sense that JavaScript is an attempt at a Self in Java's clothing.
Eich did a podcast on the history of JS a couple years ago. If memory serves correct, he said that Netscape did try to put Java in the browser alongside JS and canvas for both to target, but only JS met the deadline.
Netscape was pretty ambitious back in the day. They did have server-side JS and JS stylesheets. I wonder how far they would have gotten had MS not decided to get in the browser game.
I don't know about the Smalltalk angle, but that would be very interesting to hear about.
Bill Joy at Sun was a JS supporter. As with pmarca, me, and others at Netscape, Bill saw the value of an analogue to fill in the `??? : Java :: VisualBasic : VisualC++` comparison.
Edit: From this video https://vimeo.com/77415896
1) "prioritized multiple inheritance": Inherit from multiple parent objects: you can inherit through any number of parent slots in a Self object, not just one __proto__ or super or whatever, like JavaScript. There are well defined rules that determine the priority of parents.
2) "dynamic inheritance": Dynamically change which parents you inherit from at runtime. In Self, changing who you inherit from at runtime is a useful programming technique, and the VM implements it efficiently (which is why Self is so interesting and has been so influential). But JavaScript sets the prototype when you "new" an object, and it stays that way.
Self is simple because there's nothing but objects, all the way down. Everything is the same kind of object. Nothing is special.
JavaScript does this weird arbitrary thing by using functions as constructors, and also as prototypes, for no particular reason. Those special constructor functions should never be called normally, they should always be called indirectly with "new". And it means you can only have one constructor. So why the hell are they functions, if you shouldn't ever call them? It's just another way to fuck up, due to pointless and arbitrary complexity.
Being that weird way doesn't make JavaScript any more powerful or expressive or efficient, just more complicated, harder to understand, and easier to make subtle inscrutable mistakes.
JavaScript punted on the important parts of Self at least as much as it punted on the important parts of Scheme (like call/cc).
However at least there's a perfectly reasonable excuse for not supporting call/cc if you have to efficiently interoperate with C code, native libraries and a garbage collector, and the entire system isn't written in pure Scheme.
But there was no legitimate excuse for screwing up the great things about Self that, if done correctly, would have made JavaScript simpler and cleaner and more beautiful.
[1] Self: The Power of Simplicity: http://www.selflanguage.org/_static/published/self-power.pdf
>Occam’s razor. Throughout the design, we have aimed for conceptual economy:
>• As described above, SELF’s design omits classes and variables. Any object can perform the role of an instance or serve as a repository for shared information.
>• There is no distinction between accessing a variable and sending a message.
>• As in Smalltalk, the language kernel has no control structures. Instead, closures and polymorphism support arbitrary control structures within the language.
>• Unlike Smalltalk, SELF objects and procedures are woven from the same yarn by representing procedures as prototypes of activation records. This technique allows activation records to be created in the same way as other objects, by cloning prototypes. In addition to sharing the same model of creation, procedures also store their variables and maintain their environment information the same way as ordinary objects, as described in Section 4.
[2] Parents are Shared Parts: Inheritance and Encapsulation in Self: http://bibliography.selflanguage.org/_static/parents-shared-...
>This paper describes the inheritance and encapsulation mechanisms we designed and implemented in one prototype-based language, SELF. Our design is based on the philosophy that an object’s parents should be treated as shared parts of the object, and that inheritance should be a simple, declarative way to maximize the possibilities for sharing. This paper describes two heretofore unpublished innovations: a prioritized multiple inheritance scheme that unifies unordered and ordered multiple inheritance, and an object encapsulation model that provides many of the benefits of class-based encapsulation in a language without classes. In addition, our inheritance system supports directed and undirected resends to forward messages to an object’s ancestors, a unique sender path tiebreaker rule that resolves many ambiguities between unrelated unordered parents, and dynamic inheritance, which allows an object to change its parents at run-time to effect significant behavioral changes due to changes in its state.
Self is an incredibly powerful, simple language. I'm still not sure if it's a good language. My own experiments with prototypes left me feeling like it's too simple and formless. It felt like building a house out of toothpicks.
My "building a house out of toothpicks" isn't too far from Alan Kay's "Lisp isn't a language, it's a building material".
I think both Scheme and Self feel like they give you the tools you can use to build your own language on top of them. Where by "language", I include the facilities for composing and abstracting code, idioms, best practices, and all of the other stuff most languages enshrine directly in their semantics. Java says "the best way to organize code is in classes".
Scheme and Self don't really say anything at that level. They give you the tools so that you can say what you want. If you want classes, you can build class systems in Scheme and Self. If you don't, you don't have to.
At one level, that's really cool — I get to be a language designer! But if you just want to ship and app and be working at the top of the stack, it sucks when you have to show up on day one and pour the foundation yourself first.
When I'm writing JS in this way, my mental model is basically "a crappy Scheme", i.e. I'll tend to come up with implementations which are similar to what I'd do in Scheme (pure functions and closures), except I have to consciously avoid boilerplate (since there are no macros to tame it) and unbounded recursion (since there's no tail-call elimination), hence the "crappy".
Maybe it helps that none of my Scheme code has used continuations, outside of examples/playing, so there's less of a disconnect there?
"Higher Order Perl" is a great book and I still refer to it as a general reference for FP concepts and techniques.
I gave the caveat of "whenever I'm using JS" since I only use JS when I have to, and that's not very often (as I say, I don't have much love for the language).
I think it's a bit inappropriate to throw out random suggestions like Kotlin when the discussion is about Javascript and Scheme. I spend most of my time in Haskell, which (like seemingly every language these days) can also be compiled to JS, and I'd wager is 'even more functional' than Kotlin. I didn't mention it because the discussion was about JS and Scheme ;)
function name() {}
definitions, you will never have the problem of confusing hoisting actually happen to you...Yes var had it's own set of problems (which you could argue make the alternative of assigning functions to variables bad) causing unexpected bugs but those have in turn been solved very well by const and let... and just like function names, there is also no reason to use var now.
In the authors comparison of the language features the relatively new `let` feature would now move one of the scheme features into the "shared with JS" subset, making it overall look a lot more like scheme...
I suspect there are some other new shared aspects as well but I don't comprehend the language feature definitions well enough to say confidently.
Hoisting variable definitions is just straight up pants on head stupid.
The way scoping for `this` works is bizarre and un-intuitive. I routinely run into people who spend a lot of time programming in JS and who I consider pretty smart and capable programmers (based on reviewing dozens/hundreds of PRs from them) who didn't understand the way it works until after I explained it to them.
let/const/arrow functions fix most of these, but it took over twenty years for those problems to get fixed. Also, they didn't exist when this article was written in 2013.