Hacker News new | comments | ask | show | jobs | submit login
JavaScript Isn't Scheme (2013) (stuffwithstuff.com)
84 points by tosh 9 months ago | hide | past | web | favorite | 51 comments

previous discussion https://news.ycombinator.com/item?id=6068360

* 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.

I think I still mostly agree with it. let was on the way in when I wrote that, but that doesn't mean lexical scoping is a particularly interesting or defining trait of JS. Most languages today have pretty solid lexical block scoping, so that's more JS playing catch-up than anything else.

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.)

BASIC was my first programming language too. And I hated it from the moment the thrill of being able to program a computer at all wore off. It kept creating itches in me that I couldn't scratch, or even name until I dove into Pascal and, later C.

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).


First were Applesoft BASIC on my Apple IIe at home, and whatever weird BASIC ran on the TRS-80s at school. Then Microsoft QuickBASIC on an early Mac, followed by FutureBASIC, mostly on a Mac LC. There was a little RealBASIC near the end before I finally switched over to C.

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.

Promises and generators are both awesome, but they're both way more restrictive than call/cc. Now, I tend to the idea that that's usually a feature - and that at most strictly delimited continuations is more than sufficient in a normal language - but equating promises to continuations is ... not really accurate.

(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)

Both promises and generators can be used to implement delimited continuations.

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've implemented delimited continuations using both. But I don't disagree with any of your factual statements.

> equating promises to continuations is ... not really accurate.

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?

Re: “let”

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)

Being able to write:

    for (let x in list) {
      callbacks.push(function () { x+3 });
instead of:

    for (var x in list) {
      callbacks.push((function (x) {
        function () { x + 3 }
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)

Oh. I guess I don’t use for...in much (at all). I see your point now, though.


    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)

I like the function scope as it encourages you to create functions. functions are great! Only time it gets confusing is in the for-loop where most people expect a closure. I tell them to just put the innards of the for-loop in a function. Also with function scope you don't have to worry about the blocks, you can declare variables everywhere and they'll be hoisted.

There are some gems in here :D

> 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).

Me too! Another favorite just below that is this:

> 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.

Someone once told me a story that when Netscape wanted to put a programming language in the browser, they went to ParcPlace (i think) and asked them if they could have Smalltalk for that purpose. ParcPlace said sure, go ahead, and it'll be $1000 a seat.

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.

Brandon Eich said he thought he was hired to put Scheme in the Netscape browser, but management wasn't interested. Meanwhile Sun wanted Netscape to put Java in the browser, but Netscape wanted their own scripting language, so they worked out a compromise, with Eich creating Javascript (I think he may have worked with one of Sun's developers for JS 1.0).

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.

We would still be waiting for XmlHttpRequest :)

Maybe so! And using layers instead of z-index.

I heard a very similar story, but it was Sun wanting to license Smalltalk. Rebuffed by very high licensing costs quoted by Adele Goldberg, they went and created Java instead.

Edit: From this video https://vimeo.com/77415896

That's interesting because Sun created Self, which is based on Smalltalk, and the VM they used for Self become the Hotspot VM. So I don't know why they went with Java just because of high licensing costs for Smalltalk. I say this before watching the video.

Ah, i've probably heard a mangled version of that story!

Whenever anyone compares JavaScript with Self, I cringe. The name of the seminal Self paper was "SELF: The Power of Simplicity" [1]. JavaScript totally missed the boat on simplicity (the entire POINT of Self), and also totally missed a couple of important features that make Self Self [2]:

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.

I wouldn't express in as delightfully salty a way as you have, but I totally agree that JavaScript is got little from Self beyond the word "prototype", which it misused.

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.

Self's inventors intended the ideas to be applied to other languages, and aspired to reform and bring about a new age of Smalltalk, but Sun co-opted it to turbo-charge Java via HotSpot, and eventually the same ideas found their way into V8 and other JavaScript VMs. So even if the language itself didn't deeply influence JavaScript, the VM did indirectly.

Couldn't Lisp be accused of the same thing? I realize that CL has a boatload of features, but fundamentally Lisp is very simple and formless in using the list form for all code and data.

I think so, to some degree.

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.

Part of its simplicity enables its metaprogramming, though. You decompose the problem downward while using metaprogramming to extend the language upward. A simple language with metaprogramming is never equal to one without because the former can be turned into anything its execution or compilation constraints allow. :)

Well, a really simple Lisp could be accused of the same thing, but part of the allure of Common Lisp is that a lot is standardised such that an implementation can optimise it in one way or another — so it's not formless in practice.

'simple' has multiple definitions. One is used as a pejorative, another as a compliment.

That's a very simple way of explaining it. ;-)

I think of JS as being Scheme-like, but not because of any love for the language. Instead, whenever I'm using JS I take a Crockford-like approach by only using a nice subset of the language: basically, closures and not much else.

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?

Heh, I have been writing "crappy scheme" in Perl (and more recently JS) for years. Perl is actually kind of nice to use as a functional programming language, once you get used to thinking of it as such.

"Higher Order Perl" is a great book and I still refer to it as a general reference for FP concepts and techniques.

I'd suggest using Kotlin then. It has tail call optimization, and nice functional programming constructs. It can also be compiled to javascript: http://the-cogitator.com/2018/04/25/going-beyond-android-kot...

> I'd suggest using Kotlin then

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 ;)

Anecdote time: A long time ago I was put on a javascript project. It was a greenfield one but I had to use js because it was a browser app. I've also heard this Scheme myth but having worked with Common Lisp I thought that this must be a mistake. So I took a look at JSON and thought that well json is homoiconic. The problem is that you don't have a homoiconic syntax for functions so I tried the eval way but it became ovbious tha this is an ugly hack at best. I concluded that js has anything to do with scheme.

I really don't get all the JS scoping hate. Especially because its quite similar to Python in that they both have lexical scoping with functions introducing new scopes, and I rarely hear complaints about Python. I guess this scoping might be annoying to people coming from C-style background, but it's really not all that big of a deal. JS even has `let` now to allow blocks to introduce new scopes too.

I don't think 'let' existed when this article was written.

Your observation is undeservedly sitting at the bottom of this thread :P but it is quite an important one:

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.

I don't like the function level scoping, but I could probably get over it.

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.

The problem (as I see it anyway) is that function scoping + hoisting is weird and can cause some unexpected bugs.

Once you learn that fact, and then avoid ever using

  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.

Python is awful. Since you haven’t heard any complaints. Maybe 3 is ok, can’t use it don’t know. Have to use 2.7 at work. Can’t make jars for Spark with Python and yes I am salty about it. Meanwhile we are on Ruby 2.5.1 because they didn’t fork the whole ecosystem with a language upgrade.

Ruby's 1.9/2.0 upgrade had pretty similar changes to Python 3. For example, in Ruby 1.8.7, 'hello'[1] returned 101, whereas now it returns 'e', and String#each doesn't even exist anymore. Similarly, string encodings became significant in both Ruby and Python's breaking changes. It seemed to me like the Ruby ecosystem just didn't drag its feet as much as Python's (probably largely because Rails had enough pull to drag the Ruby ecosystem along with it, and Rails enthusiastically supported Ruby 2).

Matz didn't change puts. It seems trivial, but I suspect making print a statement again would do a lot to speed up the Python3 adoption rate.

Python and JS are both equally awful. The sad reality is that Perl 5 still remains the only viable scripting language aside from scheme (lol) with sane scoping and lexical closures.

I think you will find that Perl 6 is even more sane in that respect.


Thanks! Updated.

Applications are open for YC Summer 2019

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact