Things to thing about when changing the language


#1

I’ve noticed a few different posts here about language proposals and additions. I am responding to a general trend, not a particular post or person, in writing up some thoughts. I’m trying to only have one foot on the soapbox here, and I’m really glad that we have some very eager people who are pushing the boundaries of what Elm can do. I want to guide you, not yell at you.

Evan has said a lot about contributing and language development in various conference talks and posts, but I’d like to highlight two things he has said (paraphrased):

Contributions are more than merged code. Experience reports, research (“how do other languages solve ___”; collections and statistics of real-world code experiencing a problem), blog posts, third-party packages, documentation, SSCCEs of bugs, and more are all great ways to contribute.

When you release a feature is part of the feature. Typically, features released earlier are more central to the language, and become part of major libraries or the basis for other features. Timing controls what parts of the language are emphasized.

Here are some of of my own thoughts on the virtues of language simplicity:

No pointless code review comments. If you could use let or where to do basically the same thing, someone is going to say you should use the other one. It wastes time and distracts from actual issues in your PR. This is part of the reason why elm-format is not configurable: this is the way to do it.

Feature interactions grow quadratically. Think of features as vertices of a graph, and the interactions between pairs of features as the edges of the graph. That means that a language with n features has O() pairs of features that could interact negatively. Adding features gets increasingly hard the more you add. (This applies to most software, not just languages.)

As a concrete example, defining a type alias of a record implicitly defines a record constructor. You can also define a capitalized identifier with a custom type. These can clash without you realizing it (until the 0.19 compiler catches the error). This is also an example of how one of the very few pieces of “magic” in Elm can cause trouble.

Language evolution is hard to predict. StandardML, from the early 1980s, uses : to mean “has type” and :: to mean list cons. Elm adopted the same meanings, but Haskell flipped them because the designers thought that lists were going to be more important than types. This turned out to be wrong, almost laughably so – Haskell is a “laboratory for types”. Similarly, Haskell uses the special syntax [a] in type annotations to mean List a. Early versions of Elm did too, until it was decided the special case was not worth it. This leads into–

Special syntax separates the language. Have you ever noticed that there’s no bespoke language feature to use HTML in Elm? It’s all lists and functions. Contrast with JSX and handlebars in the JavaScript ecosystem, and ERB and HAML for Ruby on Rails. You have to learn crazy new syntax, and frequently some kind of “partial” system because you can no longer, oh, call a function. In Elm, you’re not constantly trying to escape from HTML to Javascript with curly brackets (*glares at JSX*); every language feature works everywhere.

Keep Elm’s type system intuitive. Elm 0.19’s type system has some nice properties. Syntactically, types are always in the type annotation and are not mixed with values. Semantically, if you start with a valid program (with a type annotation), you can remove that annotation and still have an identically-behaving valid program. Let me give an example of when both of these properties are violated.

Here’s some Elm code that finds the last even number in a list (silly example, but go with it):

[1,2,3,4,5] |> List.foldl
  (\nextNumber lastEven ->
    if (nextNumber |> modBy 2) == 0
    then Just nextNumber
    else lastEven)
  Nothing

Here is equivalent Scala code:

List(1,2,3,4,5).foldLeft(None){
  case (lastEven, nextNumber) =>
    if (nextNumber % 2 == 0){
      Some(nextNumber)
    } else {
      lastEven
    }
}

Oh wait, this Scala code doesn’t compile. There’s a mismatch between Some[Int] and None.type. To get this code to compile, you must write the first line with an explicit type ascription in the code: foldLeft(None: Option[Int]). I don’t think anyone is proposing a change that would cause Elm to require annotations, but the Scala core team is neither stupid nor lazy – in order to support their more sophisticated type system, they had to trade away 100% inferable types. I opine that we shouldn’t do that.

Jon Bentley and Doug McIlroy are quoted as saying, “the key to performance is elegance, not battalions of special cases,” and I think the same is true of developer experience and ergonomics. We have a small number of extremely useful and flexible tools; chief among them are pattern matching on custom types, inferred static type checking, and first-class, curryable-by-default functions. It may take a little bit of boilerplate, but I think that by exploiting what’s already in the language, we will do a lot better than adding many “helpful” features.


#2

Well said, and I think you have made some very good points. The simplicity and minimality of Elm syntax is a big appeal for me (as it was for ML).

I have a book on Scala that is over 1000 pages long! Its a kitchen-sink language. There was a trend in language design when I was a student to develop languages where a specification could be written for the language that was around of 50 pages long. ML was one such language, as was Modula-3.


#3

Good points! I particularly like the one about special features.

It’s really impressive that there are so few syntactic constructs in Elm and that they’re used so consistently. I love that List, Maybe, and Bool can be defined in user-land with custom types rather than being syntax baked into the language :heart_eyes:


#4

Excellent points. From the user’s perspective, having a small language is a big plus. If you spend a total time T learning and practicing the language, and if there are n features, then one will spend an average time of T/n on each. When n is small, one can become a virtuoso practioner/real expert much more quickly than when n is large.

The analysis is a bit simplistic, but there is some truth to it. Personally, I haven’t found lack of language features to be a problem. The developments that have had the most impact on what I am doing are (a) the 0.19 compiler – speed is awesome, (b) new libraries such as File and Bytes.


#5

I gave a talk last month introducing Elm to a functional programming group. As they were already well versed in FP, I gave them a complete overview of Elm syntax. Amazingly, this only took 2 slides to do, and I managed to use every Elm syntax construction in that (apart from the WebGL shaders stuff). Elm is so simple and elegant, I have to say probably the closest any computer language has come to ‘perfection’, in my very biased opinion.


#6

Could you share your slides?


#7

Even better, here it is as a Gist, so you can fork it and improve it, as there are certainly more things that could be added:


#8

Thanks so much — I’ll use this at the next Elm meetup!

Jim