|
|
I'm sure every programming language has them. But hanging out on Stack
Overflow (and reading 8K rep by the way), I've noticed certain questions
reappear with extreme frequency. For your amusement, I have attempted to
collect the most common ones...
* Using tab characters instead of spaces.
Whitespace is used to delimit scope in Haskell. A space character is 1
space wide; a tab character is a different randomly-chosen size in every
program known to Man. That means that text which appears to line up in
your text editor may look completely wrong in a different program - or
to the Haskell compiler, for that matter.
This problem is nauseatingly hard to diagnose through a website. Usually
when the person copy-pastes the code to the Internet, they inadvertently
fix the actual problem in the pasted version.
* Other whitespace problems.
There are lots of other mistakes people make, where they write perfectly
valid code which fails to compile merely because of an extra space here
or there.
* Letter case errors.
Variable names MUST begin with a lower-case letter. Types and classes
MUST begin with an upper-case letter. This is not merely a coding
convention, it's actually how the compiler determines whether you're
trying to access a variable or a type or whatever. So while C / C++ / C#
/ Java / VB / whatever let you use any convention you please, in Haskell
this can actually stop your program compiling at all - often with some
very strange error messages.
* Missing out "else"
Haskell's if/then/else works like the ternary operator in C / C++. It is
an EXPRESSION, not a STATEMENT. You CANNOT omit the else-clause; what
would the value of the expression be if the test fails?
* Calling functions with tuples.
In anything derived from ALGOL, a function call looks like
foo(1, 2, 3)
But in Haskell, the correct syntax is
foo 1 2 3
This works exactly like Bash scripts; the first "word" is the function
to call, and all subsequent words are arguments. This becomes slightly
confusing because you can pass a function as an argument to another
function. That means that the first word is the function to call, but
the other words might ALSO be functions. Regardless, the FIRST word is
always the function being called.
Unless it's an operator. (cf. 2 + 2)
Oh, and although words default to prefix and operators default to infix,
you can override this. Very flexible, but also potentially confusing...
* Trying to make trees out of lists.
"I tried to write a function to convert [1, [2, 3], 4, [[5], [6, 7], 8],
9] into [1, 2, 3, 4, 5, 6, 7, 8, 9], but I can't get it to compile."
The problem is nothing whatsoever to do with the actual code, it's the
input. A variably-nested list like this is ILLEGAL in Haskell. It's
ill-typed; every element of a list must have an identical type
signature. And, naturally, integer /= list of integer /= list of list of
integer.
* Incorrect let-block syntax.
The correct syntax for a let-block is
let x = 1 in <expression>
However, inside a do-block it is permissible to write
let x = 1
The REPL also accepts this form. But in a source file you can't write
this outside of a do-block. This appears to be a common mistake.
(Fortunately, it's one that's very easy to explain.)
* Incorrect where-block syntax
The correct syntax is
...stuff...
where
...more stuff...
However, people keep trying to nest where-blocks. This is a bad idea;
it's quite hard to get right, and it's not NECESSARY in the first place.
[Usually.]
* Operator precedence mistakes.
This can bite you in any programming language. But a language that makes
extensive use of large, complicated expressions is particularly
troublesome. Examples:
Code: putStrLn "Foo = " ++ show foo
Means: (putStrLn "Foo") ++ (show foo)
Intended: putStrLn ("Foo" ++ (show foo))
Code: sin 2*x
Means: (sin 2)*x
Intended: sin (2*x)
Code: print -2
Means: print - 2 (I.e., subtract 2 from print.)
Intended: print (-2) (I.e., print out -2.)
Notice how all three of these are the exact same bug; function
application has the highest precedence of any operator, which apparently
isn't what people are expecting. (The final example is arguably a bug in
the language specification, but there you have it.)
* Detecting types at run-time.
"How do I write a function that takes one argument and detects whether
it's a String or an Integer?"
Well, um, such a function can't exist in the first place? I mean, the
type signature SPECIFIES exactly what type of value will be fed in at
compile-time, so how could you not know at run-time?
A similar question is "how do I write a function that accepts any type
of data and prints out its type?" Haskell simply doesn't support such
things.
* Translating object-oriented code into Haskell.
"Hi. I tried to translate this [large] Java example into Haskell. I
wrote <long tangled list of Haskell classes and datatypes>, but it
doesn't compile / doesn't work. Help!"
Any programmer with any sense ought to realise that trying to
"translate" code from one language to another which has a radically
different paradigm is a very bad idea - ESPECIALLY for a beginner! If
you're a Haskell expert, by all means give it a try. But if you're
trying to learn the language for the first time, don't learn how to
write bad Java code in Haskell, learn how to write Haskell code in Haskell!
(I would give the same advise to somebody trying to "translate" a
Haskell program into Java. It's simpler to redesign from scratch.)
* Infinite loops
Recursion is very common in Haskell. A lot of people apparently don't
understand how to make the recursion terminate eventually - perhaps they
think this happens by magic or something. A definition such as
map f (x:xs) = f x : map f xs
will throw a pattern-match failure exception when the end of the list is
reached. But it's quite easy to do something like
fibonacci n = fibonacci (n-1) + fibonacci (n-2)
This recursive loop simply never ends, exhausting all available RAM
until the user has the sense to kill it. The fix isn't hard, but
understanding the problem is apparently problematic for many.
* Monads.
"How do I convert IO Integer into Integer?"
Please go and read a basic tutorial explaining how I/O works in Haskell.
(This particular question also seems to be particularly correlated with
people who don't really understand Haskell syntax. I'm not sure why...)
* The "return" function.
In anything ALGOL-derived, the "return" keyword causes an immediate exit
from the current function, optionally specifying a return value. In
Haskell, it's not a keyword, it's a function, and it does something
UTTERLY UNRELATED.
Namely, a monad is a thing that can produce a value, and "return x"
constructs a monad which will produce x. This USUALLY happens at the end
of a do-block, as in
foobar a b c = do
x <- foo a b
y <- foo a c
return (x + y)
But actually return is valid anywhere. One can write, for example,
x <- return 5
which is the same as "x = 5".
It's not so much that what return does is confusing; merely that it's
name leads people to expect it to work like other programming languages,
when it's totally different. Simply renaming this function would
probably fix the problem.
* Record name collisions.
In (say) C, you can write
struct {int Foo, int Bar, int Baz} Foobar;
struct {bool Foo, int Wok} Fizz;
Lots of people try to write Haskell like
data Foobar = Foobar {foo :: Int, bar :: Int, baz :: Int}
data Fizz = Fizz {foo :: Bool, wok :: Int}
This doesn't work at all, due to a namespace collision. Basically when
you define a named field, it also auto-generates a function to fetch the
value of that field. That is,
foo :: Foobar -> Int
bar :: Foobar -> Int
...etc...
Naturally, there can only be one function in scope named "foo". The net
upshot of this is that no two records may ever have fields with the same
name. This is widely considered a language design bug, but nobody has
yet come up with a satisfactory solution to the problem.
* Special GHCi behaviour / defaulting rules / the monomorphism restriction.
GHCi, the Haskell REPL, behaves very slightly differently to how normal
compiled Haskell works. You get lots of questions regarding strange
differences in behaviour between code written in a source file, and code
executed interactively. GHCi is slightly more forgiving, in the
interests of letting you quickly test stuff without having to be really
pedantic about everything; GHC demands that you say what you mean.
* Strictness bugs.
"I wrote this simple program, but when I give it more than a handful of
inputs, it eats several GB of RAM and then crashes my PC!"
This is arguably the single greatest weakness of Haskell. It's
frighteningly easy to write a program that defers huge amounts of work
until the very last minute, and wastes vast amounts of RAM doing it.
Often when you finally try to output a result, a stack overflow occurs
as the run-time tries to recurse down a bazillion levels of indirection.
The fix for such bugs is sometimes frighteningly simple. I'm fond of the
time I took one of my programs, added one single character to it, and it
went from taking 20 minutes to taking 0.02 seconds to produce the same
result. It's hard to make bugs of that magnitude in (say) Java or C++.
(On the other hand, sometimes the fix is terrifyingly difficult too!)
* Mutating data
"I deleted an item from this list, but it's still there!"
Um, no, no you did not.
"OK, so how do I do that?"
You can't. It's impossible. This is a design feature. Please go read
about what functional programming is and why that's desirable.
* Writing a Haskell compiler.
"It's a simple beginner's exercise to write a simple Scheme interpreter.
How hard would it be to write a Haskell REPL / compiler?"
Um, well, consider the following Haskell language features:
- Whitespace scoping.
- Arbitrary user-defined operators with user-defined precedence.
- Automatic type inference.
- Automatic code generation for system classes.
...yeah, er, this is not a five minute toy project. By all means, throw
together something that parses and executes a small subset of Haskell.
But if you want full Haskell 2010 compliance... yeah, that's gonna take
you a little while.
Post a reply to this message
|
|