This kind of comment about Elm is quite hard to understand without context. Have you ever used a typed language before? Have you ever used a pure language before? Is there something special about Elm that gives you this kind of benefit, or do other languages do the same thing?
Overall, I really like the approach of making error messages an important feature. We see them so often that improving them makes the developer experience much better, but I can imagine they're a less 'sexy' task to work on and probably much harder to get right than you'd expect.
I've used typed languages, and pure ones, but what I've found to be really special about Elm is that the compiler is super helpful. This also makes learning the language really enjoyable, as you've got someone helping you along. :)
Without having used Elm that much myself: One of its features is its very user friendly error messages. I imagine the author is comparing with more dynamically typed languages, where when you leave your work in the middle of something, there's usually a bit of digging when reopening it and continuing work, which he contrasts with Elm, where the compiler is quite excellent at reporting your unfinished work (in the form of helpful type errors), allowing you to quickly grasp at what point in your program you were at.
Not the author but I've had similar experiences. I haven't used Haskell or OCaml in my day job (though I would like to if given the opportunity). I have written some trivial and slightly serious programs in both of these languages. I've even worked up a few small programs in Agda.
Elm, IMHO, is in a class of its own. There's a strong focus in the design of the language to be usable and friendly to the developer. The focus on structural typing, the error messages, and the tooling all go a long way to bring the developer from beginner to expert with as little friction as possible.
In contrast I find this is the biggest hurdle for Haskell and OCaml. It's simple enough in either language to get started but there's a steep learning curve to becoming productive and the tooling, documentation, and even the design of the prelude libraries are the largest stumbling blocks for transitioning from an intermediate developer to an advanced one.
EDIT: I didn't mean to say that Haskell is as easy, or that the error messages are as good. I just mean that the error messages can give you a nice reminder of what you were working on, which I believe was the point of this post.
I didn't think that ease of understanding was necessarily the point. My point was that once you do understand them, it sort of tells you what to do next.
That said, in a recent HN post somebody said that the latest version of GHC has better error messages, which I agree would be welcome, but I haven't tried it.
Haskell has... precise... error messages. As I learn more about it, the errors are become more useful, and driving my development in a similar way to with Elm, however with Elm the learning curve is far shallower and the compiler helps you up it, whereas with Haskell I'm always just googling types to figure out what the compiler is trying to tell me.
Many of the error messages become clearer with experience, but this is mostly due to developing an intuition for what is actually wrong with your code based on the error message you see, not due to the helpfulness of the error message. Often the actual source of the error is in a different place than what is reported. Consider what happens if you mean to concatenate two strings but forget to put `++` in there:
Prelude> "foo" "bar"
<interactive>:3:1:
Couldn't match expected type ‘[Char] -> t’
with actual type ‘[Char]’
Relevant bindings include it :: t (bound at <interactive>:3:1)
The function ‘"foo"’ is applied to one argument,
but its type ‘[Char]’ has none
In the expression: "foo" "bar"
In an equation for ‘it’: it = "foo" "bar"
Yikes. Even worse, consider a similar situation with numbers:
Prelude> 1 2
<interactive>:2:1:
Non type-variable argument in the constraint: Num (a -> t)
(Use FlexibleContexts to permit this)
When checking that ‘it’ has the inferred type
it :: forall a t. (Num a, Num (a -> t)) => t
Also the parser errors in Haskell are terrible. That the community has so long put up with "parse error (possibly incorrect indentation or mismatched brackets)" is a marvel to me, and is one of the most irritating errors to fix because of the (at least apparent) simplicity of improving it.
The first error message is good: it helpfully includes the exact expression that craps out, and it means, matching intuition about how Haskell should be compiled, that the expression "foo" "bar" could only make sense if "foo" were a function from [Char] (i.e. the type of "bar") to some type t, but unfortunately "foo" is a [Char]: everyone should understand it easily, even if gratuitously introducing other names (it and its type t) and details about the compiler making up type equations is highly inelegant.
The second error message, on the other hand, proves that the first one is a lucky accident.
Everything you say is true, but it took me about 90s of staring at the Haskell error before I parsed out exactly what it was objecting to, even though I knew exactly how the code was broken. Whereas it took me no discernible time to understand the Elm error messages. Some of it could be that parsing the Haskell message primed me for the Elm message, but it doesn't seem that way to me.
>The second error message, on the other hand, proves that the first one is a lucky accident.
No, the second error message just shows that there's more options there so the error message is vague and not very helpful. The first one is not an accident, it is the standard type mismatch error.
My point is that 1 2 should give the same type of error message as "foo" "bar", because it is the same kind of error (1 isn't a function with one or more arguments).
The compiler exposes that when numbers are involved it attempts (and fails) some fancy and unexpected reasoning involving forall, while with plain type constructors like [Char] it attempts (and fails) straightforward and intelligible pattern matching.
Figuring out the type of 1 and 2 might be more difficult than figuring out the type of "foo" and "bar", but the special rules should be hidden: a compiler that cares about practical usage would provide unified and clear error messages, possibly listing sets of alternative types it was unable to choose from.
Not saying many Haskell compiler errors are obscure, especially for the uninitiated, but the particular error you picked as your first example is perfectly clear.
Haskell is telling you 'you tried to apply a string literal to an argument as if it were a function, but it's not! It's a string literal. By the way, the string I'm talking about is "foo", which you tried to apply to "bar" in the expression "foo bar"'.
The output contains all you need, yes, but it would be nice if what you typed was added to the output. You wrote out a much more user friendly version.
Agreed, Elm in these two examples is way nicer! It doesn't add information, but it presents it in a beginner-friendly way, which is commendable.
However, I'm left wondering... Elm seems to be using hardcoded knowledge about commonly used types such as strings and numbers in order to improve its error messages (I wonder why Haskell doesn't, by the way). What happens in Elm when an error occurs with more complex, user-defined types and operations?
Something I've just found is that the errors can also be output in a nice machine readable format, so the suggested fixes can be read in by your editor plugin.
And finally, this is wonderful, a specific repo for code that causes error messages and a request for people to submit error messages that are confusing: https://github.com/elm-lang/error-message-catalog
Well like I said, once you see enough of these error messages you get a clear idea of how to parse them and understand what they're "really saying". But on first glance, and in particular for newcomers, the error message is rather arcane. And of course, in this instance it's quite straightforward because it's a very simple expression, with a rather clear problem. But consider when the error might be buried in a much more complicated expression, or the fact that it's not a function (or is a function, when you expected otherwise) might not be as clear. For example a curried function that has too many or not enough arguments applied.
Also, although from a compiler's point of view it might make sense, the wording "The function ‘"foo"’ is applied to one argument, but its type ‘[Char]’ has none" doesn't make sense. It's clearly not a function if it doesn't take an argument. The same error message would be far clearer if it said something similar to "The expression ‘"foo"’ is being used as a function, but it is of type ‘[Char]’".
Also these are just two examples I rattled off the top of my head; the fact is that while the compiler is incredibly helpful in making sure your code is correct, it's less helpful in explaining itself. But, I also realize that producing good error messages is hard, and (I assume) significantly less interesting to many on the GHC team.
> "foo" "bar"
-- TYPE MISMATCH --------------------------------------------- repl-temp-000.elm
You are giving an argument to something that is not a function!
3| "foo" "bar"
^^^^^
Maybe you forgot some parentheses? Or a comma?
Also
> "foo" + "bar"
-- TYPE MISMATCH --------------------------------------------- repl-temp-000.elm
The left argument of (+) is causing a type mismatch.
3| "foo" + "bar"
^^^^^
(+) is expecting the left argument to be a:
number
But the left argument is:
String
Hint: To append strings in Elm, you need to use the (++) operator, not (+).
<http://package.elm-lang.org/packages/elm-lang/core/latest/Basics#++>
How do compiler errors look in Elm with generic functions involved, about which the compiler doesn't have any helpful hardcoded knowledge? (I.e. no "+" or strings involved).
Regarding hardcoded messages, there's been at some point talk in the community regarding allowing library authors to have some sort of hook to compiler that would make it possible to customize specific error messages along with a package.
Regarding hardcoded messages, there's been at some point talk in the community regarding allowing library authors to have some sort of hook to compiler that would make it possible to customize specific error messages along with a package.
No, Elm is definitely less complex and generally targetted for newcomers to the FP and pure world.
In haskell there are like a 100 ways of doing anything and it's very hard (for me at least) to figure out where even to begin. Elm tries to reduce options and lets noobs like me get going.
It seemed to me like elm reduced options all the way to zero for way too many things. Trying to do basic tasks that are trivial in ocaml and haskell ended up with me giving up completely when trying in elm. Just generating random sequences was ridiculous.
For what it's worth, generating random values is slightly easier with the introduction of subscriptions in 0.17. If that's the version you tried, it would be nice to see why you think Elm is unnecessarily complex compared to Haskell (which is also pure) when it comes to generating random values.
> Is there something special about Elm that gives you this kind of benefit
Evan cares about compiler messages, and as a result Elm's compiler errors are the most friendly, readable, helpful and actionable messages I know of, even compared with projects with good error messages like Clang or Rust.
Most other typed functional languages don't come even remotely close, best case scenario is they're fairly complete once you've learned to decrypt them, Elm's compiler error messages have almost no learning curve. Though it may help that Elm is wilfully limited abstraction-wise.
This has always been my experience with TDD, at least if you always leave yourself a failing test when you go. A lot more labor-intensive, since you are essentially writing the "compiler" as you go, but a similar effect.
I think "gradual typing" like Flow might be a nice middle-ground.
You're still free to quickly sketch out ideas without worrying about type checking, but you can solidify things by just adding /* @flow */ to the top of a file and fixing the errors.
I haven't actually tried Elm though, so maybe a "middle-ground" isn't necessary.
This is the promise of type inference. Languages like Elm that do a good job inferring types are able to play out potentially invalid implications of how your code uses values without requiring you to separately specify all your types in great detail. You'll spend some time just addressing edge cases as "safe to ignore" but it's a lot lower than explicit typing while delivering many of its benefits.
This will sound counterintuitive to dynamic language people, but prototyping is actually much easier with types. I often find myself rewriting 50 to 75% of the codebase in the early stages of prototyping, and without types I'd be much less willing to do that, settling for a less than ideal design. This design will then be the base of a potentially large project, which amplifies early mistakes even more.
I find it's conductive to rewriting that I can iterate with only one or two codepaths of the remodeled core working before settling on something and fixing the rest of the codebase to match.
I think it's a matter of personality. Dynamic language people (to the extent there are such people... most of us use both) are generally not scared at all to change things. We're just more comfortable with the intervening chaos. For instance I might want to change some underlying model code without worrying that it breaks every single page in the site except the one I'm using to work on it, or the individual test. Sometimes I want to work on the heart of the matter first and clean up details later instead of working on the compiler's set of priorities.
Sure. In Haskell for example you can turn type errors into warnings with a compiler flag. If you then encounter a type error at runtime the program will crash, much like in a dynamic language. And if it turns out the change was a dead end, just git checkout master and you're done.
However, if not, you'll still have a list of compiler errors you have to fix to make the rest of the program work. This alone lets me refactor with confidence, much more so than every dynamic language I've ever used.
For me types make prototyping WAY faster, especially if "throw" or some other keyword satisfies the return type of a function. I can write out the function signatures and data types I need and get things to line up with the help of a tool that tells me exactly what I need to do (the compiler). This lets me quickly find a solution without having to actually implement any of it, and then I have a skeleton ready to fill in with details.
>You're still free to quickly sketch out ideas without worrying about type checking,
That's a terrible work flow though. Quickly sketching out ideas is so much easier with types. So much so that you do that with just types, you don't even need to write the code. Then once you've worked your intuition into something that could actually work, you fill in the code.
The title is unfortunate. At first I though...wait why are they discontinuing Elm, what did I miss.
I've never written anything in Elm but that short post makes me want to try it. Sounds like a pretty great feature of a language to have easy to follow error messages you can readily pick up. This also made me chuckle
"""I thought Elm was a pure, functional language, but this is one hell of a side effect."""
The error messages in Elm really are outstanding. I've never used a language that has such clear, concise, and human-friendly error messages. It's a high water mark, and I hope other programming languages are inspired to try to equal what Elm has accomplished.
ok nice. We know that. I'm personally touching Elm only after they have something like ComponentDidMount from React or something which is better implementend than "wait 50msec and do something like set focus" on their (virtual) DOM rendering is solved: https://github.com/evancz/elm-todomvc/blob/8be8914582870d599...
I'm not sure this is a fair criticism. The nice thing about this tiny chunk of code is that there is only one aspect of this example program that needs to manipulate the already instantiated DOM, and that tiny chunk is explicitly brought up to the top level and separated and connected back to the program via the port mechanism. It would certainly be worse if this magic happen behind the scenes.
Of course it could be better, and that's probably what you are getting at. Ideally the machinery provided for building web applications would be more aware of the nature of web applications and have something in the box (still explicit and obvious, but in the box) to do something like setting focus.
It's absolutely a fair criticism. The real DOM is stateful, elm's virtual DOM is not, and elm doesn't provide any tools to manipulate the state of the real DOM. This impedance mismatch is bound to cause problems any time you are dealing with the stateful aspects of the DOM. And solutions like the one linked to by GP are error prone and fragile.
Elm gives you the ability to hook into requestAnimationFrame, which allows you to execute some native js through a port when a piece of html first renders.
This is certainly annoying at the moment (although there are workable hacky solutions). However it's a well known issue and there is movement at https://github.com/elm-lang/html/issues/19 so hopefully it will be addressed in the near future.
This is the perfect example of the content-free HN posts that are topping the charts lately. It contains not a single example of a "helpful error message." Nor a single line of code from the language in question. Just a 173 word free-verse poem with some feel-good handwaving.
If this were a post about soap (the salt, not the protocol) we'd downvote the post as obvious content-free shilling. But because it's some obscure language that doesn't even make the top 100 TIOBE language list, here we are, upvoting someone we've never heard of, celebrating his opinion of awesome compiler error messages that none of us has ever seen, for a language that nobody uses.
I think this is a case where the topic is something that enough people want to discuss so the post generates discussion regardless of the content.
I agree with the basic gist of your comment, there isn't much content here to discuss (no offense to the author, it doesn't seem like it was meant for a site like HN). Though I think you might be over-reacting a bit with the tone of your comment.
You should always let something else 'drive' your coding. Lots of people like tests. I personally am not a huge fan of TDD, though it's better than a lot of alternatives.
Essentially, you set a visible goal. Passing test, compiling program, running command, visible widget on a browser. Run the test / command / browser URL and watch it fail. Let the error message you get determine your next step.
It makes a good debugging workflow too. The first step is to get a reproduction. Then you need to set up an environment where you can repeatedly and idempotently reproduce the bug at will. Then all you have to do is keep trying and learning things about the system until you know enough to devise a fix. Here you can manipulate the command itself to exercise less of the system, giving you a clearer idea of where the bug is.
Whereas if you're building a new feature, you want to keep the command static until you see what you want to see.
There's nothing magic about programming languages or paradigms that allows you to do this more easily. It's just how well you know your system. Some domains are trickier. You need special tools and expertise to reproduce network errors, and God help you if your stack isn't memory safe.
Elm is a great language for web. It's evolving but there are some bits that need some rework/improvement/rethought like the canvas performance and managing random number generation. This is difficult with purely functional architecture. But there are a quite a lot of good thought gone into making the whole functional programming user friendly and a pleasant experience. Kudos to the creators/maintainers
in my experience, when I am stuck figuring out types, I just let the compiler do it. As the error messages are so well-formed, it tells me exactly which lines are inconsistent and how to put the types back together.
most compilers tell you there's a problem but none outline how to fix it the way Elm does
To me, the most promising thing about elm is how much improvement it has seen. The last minor version was a huge leap forward for building real things AND for beginners. The makers of elm (seems to mostly be Evan Czaplicki) think about things the same way I do, except they are thinking about it more than I do and more carefully. Whether it's elm or anything else, I'm glad the tool-explosion has let more people use tools they vibe with. Hopefully it will help more random people (like me) do more cool stuff.
72 comments
[ 0.20 ms ] story [ 134 ms ] threadOverall, I really like the approach of making error messages an important feature. We see them so often that improving them makes the developer experience much better, but I can imagine they're a less 'sexy' task to work on and probably much harder to get right than you'd expect.
Elm, IMHO, is in a class of its own. There's a strong focus in the design of the language to be usable and friendly to the developer. The focus on structural typing, the error messages, and the tooling all go a long way to bring the developer from beginner to expert with as little friction as possible.
In contrast I find this is the biggest hurdle for Haskell and OCaml. It's simple enough in either language to get started but there's a steep learning curve to becoming productive and the tooling, documentation, and even the design of the prelude libraries are the largest stumbling blocks for transitioning from an intermediate developer to an advanced one.
update: grammar
EDIT: I didn't mean to say that Haskell is as easy, or that the error messages are as good. I just mean that the error messages can give you a nice reminder of what you were working on, which I believe was the point of this post.
That said, in a recent HN post somebody said that the latest version of GHC has better error messages, which I agree would be welcome, but I haven't tried it.
The second error message, on the other hand, proves that the first one is a lucky accident.
No, the second error message just shows that there's more options there so the error message is vague and not very helpful. The first one is not an accident, it is the standard type mismatch error.
The compiler exposes that when numbers are involved it attempts (and fails) some fancy and unexpected reasoning involving forall, while with plain type constructors like [Char] it attempts (and fails) straightforward and intelligible pattern matching.
Figuring out the type of 1 and 2 might be more difficult than figuring out the type of "foo" and "bar", but the special rules should be hidden: a compiler that cares about practical usage would provide unified and clear error messages, possibly listing sets of alternative types it was unable to choose from.
And my point is that no it should not. "1 2" is more ambiguous, it could be more things, hence there is a more ambiguous error message.
Haskell is telling you 'you tried to apply a string literal to an argument as if it were a function, but it's not! It's a string literal. By the way, the string I'm talking about is "foo", which you tried to apply to "bar" in the expression "foo bar"'.
Yikes? It seems pretty helpful to me :)
Your second example is better, though.
Someone posted the output from Elm, which does this: https://news.ycombinator.com/item?id=11848467
Edit: checkout the elm error message page here http://elm-lang.org/blog/compilers-as-assistants
However, I'm left wondering... Elm seems to be using hardcoded knowledge about commonly used types such as strings and numbers in order to improve its error messages (I wonder why Haskell doesn't, by the way). What happens in Elm when an error occurs with more complex, user-defined types and operations?
If you're interested, there were a couple more examples shown elsewhere in this thread:
http://elm-lang.org/blog/compiler-errors-for-humans (an older version it seems)
https://twitter.com/st58/status/732908457217531904
https://twitter.com/GregorySchier/status/732830868562182144
Something I've just found is that the errors can also be output in a nice machine readable format, so the suggested fixes can be read in by your editor plugin.
And finally, this is wonderful, a specific repo for code that causes error messages and a request for people to submit error messages that are confusing: https://github.com/elm-lang/error-message-catalog
Also, although from a compiler's point of view it might make sense, the wording "The function ‘"foo"’ is applied to one argument, but its type ‘[Char]’ has none" doesn't make sense. It's clearly not a function if it doesn't take an argument. The same error message would be far clearer if it said something similar to "The expression ‘"foo"’ is being used as a function, but it is of type ‘[Char]’".
Also these are just two examples I rattled off the top of my head; the fact is that while the compiler is incredibly helpful in making sure your code is correct, it's less helpful in explaining itself. But, I also realize that producing good error messages is hard, and (I assume) significantly less interesting to many on the GHC team.
How do compiler errors look in Elm with generic functions involved, about which the compiler doesn't have any helpful hardcoded knowledge? (I.e. no "+" or strings involved).
Regarding hardcoded messages, there's been at some point talk in the community regarding allowing library authors to have some sort of hook to compiler that would make it possible to customize specific error messages along with a package.
Regarding hardcoded messages, there's been at some point talk in the community regarding allowing library authors to have some sort of hook to compiler that would make it possible to customize specific error messages along with a package.
In haskell there are like a 100 ways of doing anything and it's very hard (for me at least) to figure out where even to begin. Elm tries to reduce options and lets noobs like me get going.
No, it was a couple years ago.
>compared to Haskell (which is also pure)
But which has both a real type system and a useful set of base libraries. The lack of basic stuff like monads is really painful in elm.
Evan cares about compiler messages, and as a result Elm's compiler errors are the most friendly, readable, helpful and actionable messages I know of, even compared with projects with good error messages like Clang or Rust.
Most other typed functional languages don't come even remotely close, best case scenario is they're fairly complete once you've learned to decrypt them, Elm's compiler error messages have almost no learning curve. Though it may help that Elm is wilfully limited abstraction-wise.
You're still free to quickly sketch out ideas without worrying about type checking, but you can solidify things by just adding /* @flow */ to the top of a file and fixing the errors.
I haven't actually tried Elm though, so maybe a "middle-ground" isn't necessary.
(nit: dynamically typed != no types)
However, if not, you'll still have a list of compiler errors you have to fix to make the rest of the program work. This alone lets me refactor with confidence, much more so than every dynamic language I've ever used.
Agreed. I can prototype an idea almost entirely in types, and if the types make sense, then the implementation often follows naturally.
That's a terrible work flow though. Quickly sketching out ideas is so much easier with types. So much so that you do that with just types, you don't even need to write the code. Then once you've worked your intuition into something that could actually work, you fill in the code.
which I think most people interested in Elm already knew.
There's also a lot less state to keep in your head, as it's usually a lot more transparent.
I've never written anything in Elm but that short post makes me want to try it. Sounds like a pretty great feature of a language to have easy to follow error messages you can readily pick up. This also made me chuckle
"""I thought Elm was a pure, functional language, but this is one hell of a side effect."""
Of course it could be better, and that's probably what you are getting at. Ideally the machinery provided for building web applications would be more aware of the nature of web applications and have something in the box (still explicit and obvious, but in the box) to do something like setting focus.
If this were a post about soap (the salt, not the protocol) we'd downvote the post as obvious content-free shilling. But because it's some obscure language that doesn't even make the top 100 TIOBE language list, here we are, upvoting someone we've never heard of, celebrating his opinion of awesome compiler error messages that none of us has ever seen, for a language that nobody uses.
http://www.tiobe.com/tiobe_index
I agree with the basic gist of your comment, there isn't much content here to discuss (no offense to the author, it doesn't seem like it was meant for a site like HN). Though I think you might be over-reacting a bit with the tone of your comment.
Essentially, you set a visible goal. Passing test, compiling program, running command, visible widget on a browser. Run the test / command / browser URL and watch it fail. Let the error message you get determine your next step.
It makes a good debugging workflow too. The first step is to get a reproduction. Then you need to set up an environment where you can repeatedly and idempotently reproduce the bug at will. Then all you have to do is keep trying and learning things about the system until you know enough to devise a fix. Here you can manipulate the command itself to exercise less of the system, giving you a clearer idea of where the bug is.
Whereas if you're building a new feature, you want to keep the command static until you see what you want to see.
There's nothing magic about programming languages or paradigms that allows you to do this more easily. It's just how well you know your system. Some domains are trickier. You need special tools and expertise to reproduce network errors, and God help you if your stack isn't memory safe.
most compilers tell you there's a problem but none outline how to fix it the way Elm does
I have dreams about it one day replacing JS as the front end language of choice.