7 ms·
Haskell error messages: come on
- deleted 5y ago[deleted]
- josephcsible 5y agoSure, the error that talks about FlexibleContexts could definitely be improved, but if you do enable that extension, then you get a much better error: • No instance for (Num [Integer]) arising from a use of ‘it’ This one can be more straightforwardly understood to mean "you can't use a list of numbers as if it were a number". Also, starting in GHC 9.2, FlexibleContexts is enabled by default, so you'll get that better error the first time.
- bnchrch 5y agoBut their lies more problems for a beginner. What are extensions? How do I find out about them? Which should be enabled? Is my code broken because of me? Or because I should enable an extension? Anyway. It’s all not caring about beginners the whole way down when it comes to Haskell and it’s community
- xhevahir 5y agoThe funny thing is, when it comes to books and tutorials and such, there's way more material available at the beginner's level than at the intermediate or beyond. Seems like after a certain point you're expected to understand everything from looking at some type signatures.
- ParetoOptimal 5y agoWhen I learned Haskell long ago I remember being handheld to adding the correct extensions by the compiler. Very rarely do I remember being misled with valid code just missing extensions.
- josephcsible 5y agoWell to be fair, AllowAmbiguousTypes is kind of infamous for being suggested when it's not what you actually want.
- Iceland_jack 5y agoAnd `ScopedTypeVariables` is infamous for not being suggested
- lpghatguy 5y agoCompare this to the error message you get from Rust with the same program though: error[E0277]: cannot add `[{integer}; 2]` to `{integer}` --> src/main.rs:2:7 | 2 | 1 + [2, 3] | ^ no implementation for `{integer} + [{integer}; 2]` | = help: the trait `Add<[{integer}; 2]>` is not implemented for `{integer}` For more information about this error, try `rustc --explain E0277`. Good error messages are important for adoption of languages with strong type systems. The complexity is already foreign enough for people who aren't familiar with type theory.
- antonvs 5y agoRust is more constrained in ways that make type inference easier and less ambiguous. One issue mentioned in the OP is the way numeric literals are typed in Haskell. Haskell also has a lot of optional language extensions, some of them quite researchy, and that complicates type inference and error messages. It also has curried functions. These can all be fantastic things to have, but they involve tradeoffs. Those tradeoffs mostly make sense in the context of Haskell's original research focus. "Adoption" of the kind you're presumably referring to was an explicit non-goal for a long time, and still is among some users. Edit: sometime else pointed out that more recent Haskell compilers give the error "No instance for (Num [Integer]) arising from a use of `it'" for this example, so this whole post may be kinda BS.
- erk__ 5y agoOcaml does also not have a bad error message: $ ocaml OCaml version 4.13.1 # 1 + [2; 3];; Error: This expression has type 'a list but an expression was expected of type int Though it is also more constrained than Haskell here
- whakim 5y agoThat error's a bit better, but not much. You basically have to know that `+` only works on stuff that implements `Num` or else it still makes no sense. Compare that to the Rust/Python errors which are so much easier to understand.
- alisonkisk 5y ago
- voidhorse 5y agoGHC error messages and needless category theory and algebra jargon are the two worst aspects of programming in Haskell. You eventually get used to both of them and, once you do, you become even more productive. Everything else about the language is magnificent....until you need to improve performance hotspots.
- whateveracct 5y agoHaskell performance ime is actually super easy. I'd say I've run into performance issues professionally 5-10 times. None took more than a day to resolve.
- voidhorse 5y agoThat's great. It's been rare for me too, but I'm currently trying to solve an issue where it seems like we've inadvertently built up some kind of mega thunk that's making performance absolutely horrible for large inputs. The hotspot in question currently implemented as big recursive loop, so it's proving hard to untangle. Normally shortcut fusion and laziness make it a non-issue but it seems like we hit a thunk chain here or something.
- whateveracct 5y agoHave you tried the various profiling modes to hint at where it is? Other than that, wolf-fencing it down is a good way to go usually.
- User23 5y agoA monad is just a monoid in the category of endofunctors, what's the problem?
- whateveracct 5y agohaha funny but my comment consisted of 1) straight-up language tooling (profiling that breaks down heap usage in various ways) 2) wolf-fencing, which has nothing to do with haskell
- brundolf 5y ago> A polymorphic enough strongly typed language is no longer strongly typed. Oof
- josephcsible 5y agoEhh, that's a bit overblown. In particular, even if you actually did make the weird list-number, you'd end up with a type error as soon as you tried to use it somewhere expecting a regular number.
- mbwgh 5y agoJust implement `Num a => Num [a]`, easy.
- josephcsible 5y agoThat would let you make the instance only be a little bit unlawful instead of totally unlawful, but I don't see how it would affect the type errors you'd get.
- asplake 5y agoAny sufficiently polymorphic strongly typed language is indistinguishable from Javascript
- valyagolev 5y agofor an opposite take https://existentialtype.wordpress.com/2011/03/19/dynamic-languages-are-static-languages/ https://existentialtype.wordpress.com/2011/03/19/dynamic-lan...
- thewakalix 5y agoThat’s not what “strongly typed” means, though.
- dmitriid 5y ago"Haskell is a dynamically-typed, interpreted language" https://aphyr.com/posts/342-typing-the-technical-interview https://aphyr.com/posts/342-typing-the-technical-interview
- Skinner112 5y ago[dead]
- Ericson2314 5y ago> Once you’ve defined lists as a number, 1 is suddenly a list if it wants to be. And this contributes to the difficulty of finding the right error message: what you asked for is possible after all. The key part is more expressive power makes good error messages harder. Maybe Rust is right to make + overloadable, but not literals, which is how it makes the better error (as described in the piece), but I am not sure. Perhaps someone uses Complex number or Fixed Point literals a lot can chime in. Racket's teaching languages are the wisest of all. Beginners and experts want different things, and there is no good reason we have our cake and eat it too, catering to their needs separately while allowing full code interop.
- LightMachine 5y agoThey're inexcusably awful. I still don't know why it is like that. Haskell gets hard things right and easy things terribly wrong.
- sfvisser 5y agoThe errors used to be somewhat better in the past, but more features have been added to the language, type system and std libs. Those features required the core parts to be slightly more expressive and generic, resulting in more abstract error messages. Not great, but I think there are definitely no easy fixes here.
- josephcsible 5y agoBut the error that this article complains about is from a compiler that's 3 years old and 4 major releases out of date, and doing the exact same thing on the latest version will give you a better error.
- gilmi 5y agoTo further elaborate, this is the error you get on the latest GHC version (9.2.1): GHCi, version 9.2.1: https://www.haskell.org/ghc/ :? for help ghci> 1 + [2, 3] <interactive>:1:1: error: * No instance for (Num [Integer]) arising from a use of `it' * In the first argument of `print', namely `it' In a stmt of an interactive GHCi command: print it
- ParetoOptimal 5y ago> The errors used to be somewhat better in the past, but more features have been added to the language, type system and std libs. The errors have improved over time in my opinion, so your comment feels like confirmation bias.
- MawKKe 5y agoGHC is like a mathematican: unable to explain mathematical jargon without resorting to more mathematical jargon
- thewakalix 5y ago> Part of what causes this is the type of 1 itself. Haskell, unlike Rust, allows literals like 1 to be interpreted in any number type. This is false. Rust allows 1 to be a u8, an i16, etc. It does distinguish between integral and floating numeric literals, but integer literals are still polymorphic. The difference is just that in Rust, numeric literals are builtin rather than extensible.
- echelon 5y agoRust does not treat integer types as interchangeable. Rust integers have a type (width and signedness) and you can't combine them without explicitly handling for overflow, etc. Any polymorphism through traits (eg. std::ops::Add) typically only operates over the same type as the source integer. If types are left unspecified, it's only because type inference knows what type you mean. Since integers are frequently used in structs and function params (places where Rust requires typing), Rust knows the type when you perform operations on these variables. Outside of these cases, Rust will generally require you to disambiguate. Rust is very deliberate about numerics.
- dmitriid 5y agoEveryone, please, for the love of God, stop saying that good error messages benefit beginners only. They benefit everyone. There are only so many things that can fit into a human's head, and the skill required to decipher indecipherable error messages end up replacing some other skill. And don't forget, these error messages will end up in the logs where you'll have to spend time figuring out what the hell happened. Edit: typos from iPhone's increasingly unusable keyboard
- galaxyLogic 5y agoWhat I find curious about the error message is that it tries to tell you what is the cause of the error, but it does not actually show you source-code that causes it. Wouldn't it be much easier to understand what is causing the error if I simply saw a short section of the source-code where I could FIX the error, by modifying something in the shown section? What is the line-number where the error occurs? Show me the code, and where it is in relation to rest of the code.
- josephcsible 5y agoGHC does do those things when you're compiling code in files. The only reason you aren't seeing it in this article is that these errors came from entering one expression at a time into the REPL.
- galaxyLogic 5y agoI see. I work in JavaScript so I don't really use a REPL. Often I do "evaluate expressions" in my IDE, I guess that is somewhat similar. I wonder if a "real REPL" would be useful for JavaScript. Or is it something that especially helps when working with languages like Haskell?
- cole-k 5y agoYeah, that's definitely a confusing part of GHCi. If you see an error message like the one in the post Prelude> 1+[2,3] <interactive>:1:1: error: • Non type-variable argument in the constraint: Num [a] (Use FlexibleContexts to permit this) • When checking the inferred type it :: forall a. (Num a, Num [a]) => [a] The "line-number" is found on this line <interactive>:1:1: error: Since it's in a REPL, it is referring to the first line you entered. The more confusing part is that the important error is found here • When checking the inferred type it :: forall a. (Num a, Num [a]) => [a] where it is assumed that you know all REPL statements that don't bind to a variable are bound to a variable named "it". So the error is really saying that the whole expression 1+[2,3] has that inferred type, and that type is bad. If you load your example into a file and run GHC on it, it will produce a still confusing error message, but it will at least show you where the problems are error-message.hs:3:11: error: • No instance for (Num [a0]) arising from a use of ‘+’ • In the expression: 1 + [1, 2] In an equation for ‘badType’: badType = 1 + [1, 2] | 3 | badType = 1 + [1,2] | ^^^^^^^^^ (I've omitted another, much longer error message it spewed out which is similarly confusing)
- cole-k 5y agoWhile I agree that this error message sucks, I would like to point out that it could be improved greatly by adding a type annotation. Prelude> :{ Prelude| result :: Int Prelude| result = 1 + [1,2] Prelude| :} <interactive>:3:10: error: • Couldn't match expected type ‘Int’ with actual type ‘[a0]’ • In the expression: 1 + [1, 2] In an equation for ‘result’: result = 1 + [1, 2] Now I agree that this might not be an ideal thing to do when showing someone Haskell on a REPL, but I will argue in favor of annotating types at least for top-level declarations in a file. Especially for beginners, as learning to follow the types I feel is helpful to understanding the language and, in turn, its error messages. I am told that well before I learned Haskell, type inference was the Cool Thing. However, I am of the opinion that a strong type system like Haskell's is best used with guidance (annotations) from the programmer. The more powerful your types, the better a specification you can write. And with a better specification, the type checker can give you better insight into what's wrong when it gets stuck.
- choeger 5y agoI've worked quite a bit with OCaml and one of the first stumbling blocks was its strictness when it comes to number literals and even operators. In OCaml, 1 + 1.0 is not well-typed. Surprisingly, even the lack of overloading doesn't hurt too much in practice, IMO. In essence, I think that Haskell went to far with its polymorphic literals, even though it's a nice idea at first glance.
- tremon 5y agoIn OCaml, 1.0 + 1.0 isn't even well-typed. You don't need to mix operand types to run into walls with OCaml. The lack of operator overloading may not hurt much in practice, but doesn't scream "elegant solution" to me. Haskell doesn't overload its operators either (in the sense of there being multiple definitions with different type signatures); instead, it has a working type hierarchy that allows the same arithmetic operators to be used for all numeric types (through Num->Integral, Num->Real, Num->Rational and even Num->RealFloat->Complex). (1::Int) + 1.0 isn't well-typed in Haskell either, but at least (a + b) is a valid operation for all numeric data types. And, most importantly, as a developer you can define your own types that also implement these same operators. OCaml does have polymorphic literals too: "%s" can be both type string and type formatter. It's just very limited and requires built-in compiler support, so it's not something you as developer can take advantage of in your own code. Although in practice I use OCaml more than Haskell, there's many times in OCaml where I wish it had more of Haskell's expressive type system.
- Tade0 5y agoBit of an OT, but: > I am a big fan of strongly typed languages > Now, in some “weakly typed” languages What does "weakly" and "strongly" typed mean here? Did the author mean dynamically and statically? Or something else?
- cygx 5y agoThe weak/strong classsification is a bit less well-defined than the dynamic/static classification, but in general, languages differ in how strongly they 'care' about their types. For example, in Javascript, you can add anything to anything, with rules that define which automatic conversions will be performed. Sometimes, hilarity ensues. In contrast, a strong language would require explicit conversions and throw an error otherwise. In languages with ad-hoc polymorphism and operator overloading, you can emulate the Javascript experience by manually adding all the different cases yourself, in some sense 'weakening' the type system.
- Tade0 5y ago> In contrast, a strong language would require explicit conversions and throw an error otherwise. That's not the definition the author is going with considering that they're saying that Python (which requires explicit type conversions in most cases) is weakly typed. From the replies here I'm seeing that there's no clear definition of "weakly" and "strongly" typed languages. I don't think it makes a lot of sense to use these terms.
- cygx 5y agoThat's not the definition the author is going with I think it is, given that he links to https://xkcd.com/1537/ https://xkcd.com/1537/ as stereotypical 'weak' behaviour. Python sits somewhere inbetween Javascript and Haskell: For example, Haskell does not allow adding floats and integers, which Python will happily do despite a potential loss of precision.
- alpaca128 5y agoRust is strongly typed, it won't let you add or compare two values of different datatypes, even if they're both integers. For example you can't add a `u8`(byte) and a `i64`(long int) without casting. Meanwhile Python, JS and many other weakly typed languages will happily add an integer and a float, maybe even a string and a number without complaints. The advantage of the former are more rigorous compile-time checks, which might catch bugs where you added/compared the wrong values. But of course it's a bit more cumbersome especially if you're not used to it. I personally prefer it, I've had numerous cases where my Python script crashed 10 minutes into some data processing because it realised I tried to add two incompatible values.
- lolc 5y agoThat has been my constant experience in Haskell. When the compiler complaints are gibberish, add types until it makes sense. It leads to this culture where people say that you have to nail down all the types. Which certainly is good advice for beginners. Still I'm sad about this because it easily doubles code with type chatter.
- gilmi 5y ago> What is my take-away here? I don’t think the compiler has been sufficiently tweaked when it comes to error messages, or that the Haskell community cares sufficiently about beginners. I don't think it's fair to draw the conclusion that the Haskell community don't care sufficiently about beginners from this, since only selected few of the community has the ability (and time) to work on GHC and improve the state of error messages.
- ubadair 5y agoI was going to say the same thing. While it may be true that beginners are not sufficiently appreciated or that error quality is not sufficiently prioritized, these extrapolations do seem a little rude in the context of what is effectively a defect report to a volunteer project.
- ValentinPearce 5y agoThis might seem silly but what is a "GC'd language" ?
- sondr3 5y agoA garbage collected language.
- ValentinPearce 5y agoSo obvious, thanks.
- tromp 5y agoAs the first comment on that article notes: $ ghci GHCi, version 9.2.1: https://www.haskell.org/ghc/ https://www.haskell.org/ghc/ :? for help Main λ> 1+[2,3] <interactive>:1:1: error: • No instance for (Num [Integer]) arising from a use of ‘it’ • In the first argument of ‘ghciInteractivePrint’, namely ‘it’ In a stmt of an interactive GHCi command: ghciInteractivePrint it In other words, this has already been fixed in GHC 9.2. More detail on the Reddit discussion of this post: https://www.reddit.com/r/haskell/comments/suinws/haskell_error_messages_come_on/ https://www.reddit.com/r/haskell/comments/suinws/haskell_err...
- nickcw 5y agoAs a Haskell beginner I have no idea what this error message means. What is `it`? Perhaps it is the argument for ghciInteractivePrint? That is what the error says. But what is ghciInteractivePrint that wasn't in the original code? I'm guessing an alias for print. The error seems to be No instance for (Num [Integer]) Which doesn't say anything about + So I'm still in the dark about this error message!
- tromp 5y agoIf you ask for the type of (+) it's Num a => a -> a -> a So it does say something about + through its type.
- ParetoOptimal 5y agoIf you presume the user knows the type of (+) which I think is kind of fair. Ideally though it would be: (+) has no instance for (Num [Integer]) The the improvement the GHC team made is already huge for those that have at least heard of Num, which I'd guess is anyone who's went through a Haskell tutorial.
- simonmic 5y agoHaskell reddit discussion: https://www.reddit.com/r/haskell/comments/suinws/haskell_error_messages_come_on https://www.reddit.com/r/haskell/comments/suinws/haskell_err... and new issue in the Haskell Errors repo: https://github.com/haskell/error-messages/issues/42 https://github.com/haskell/error-messages/issues/42