Haskell isn't difficult
2014-07-25
People like to say that learning Haskell is hard, because a pure, lazy, functional language isn't something most people are used to. The simple fact of the matter is that most people (nowadays, at least) will be introduced to programming through Python, Javascript or maybe even Java or C#. The problem with learning dynamically typed languages that don't tell you
about types is that you don't get to see an integral part of how your program works. It's worse if you program in a weakly typed language like Perl or Javascript - you won't have any concept of types unless you read around, which is a poor substitute for actually using types.
"But I tried to learn Haskell and it was hard!"
Most people who try to learn Haskell fail to "get it" for a while. After writing programs in Haskell, going through a few tutorials, seeing some examples of code, and having the power of purity and lazy evaluation click, people tend to find Haskell a lot easier. The problem is that most people don't get past the initial barrier to entry, which is "unlearning" the paradigms they're used to.
Coming from Java and C#, they'll have to unlearn that style of OOP and get used to Haskell's way of doing things. Coming from Javascript and PHP, they'll have to learn about types. In fact, unless the beginner we're talking about comes from ML, OCaml or similar, they will have to get used to how much types are used in Haskell in ways they may not be used to.
Nullable Types
In C, let's say, you might want a function which takes a name and does something with it, returning something else, perhaps a boolean indicating whether the name is valid and in some database somewhere:
bool validate(char* name)
{
// some code
}
That is all well and good, but what if the pointer passed to the function, instead of pointing to an array like you'd expect, is null? You'd have to add in a null check.
bool validate(char* name)
{
if (name == NULL)
return false;
// some code
}
That's not terribly shocking, but it's a bit inconvenient. That seems like a low-level problem you'd never get in a higher-level language like Java, for example. That is wrong. A similar function would look like this:
bool validate(String name)
{
if (name == null)
return false;
// code
}
Why are our types nullable? That seems like a terrible design decision which results in NullPointerExceptions for everyone. Abstracting the hardware away hasn't solved this problem automatically, it seems.
In Haskell, types are not nullable. You can instead use optional types. For example, if you have a function that could fail, you either return "Just" the answer or "Nothing":
validate name = if condition then Just answer else Nothing
While this sort of thing can and is done in other languages, beginners tend not to be exposed to them. Even if they were, the fact is that most code out there is riddled with checks for null.
Even worse, most people are unaware of how easy it is to code such a solution when you have a good algebraic type system:
data Maybe a = Just a | Nothing
In other languages, this sort of thing would probably done via sentinel values, which brings with it a whole host of problems. Beginners would have to unlearn that habit, because it's all to easy to code a "getIndex" function that returns "255" when there's an error, despite the fact that 255 is a valid list index for an element.
Loops? What loops?
This really threw me off when I started learning Haskell. Back when I started to learn Haskell, I thought it looked simple enough to write a simple text adventure in. In Python, such a game could be boiled down to:
player = Player()
while player.alive:
# get input, update world etc
The key part is that there is a main game loop. This is how it is in most games: there is some sort of loop that is run over and over until the player dies or quits. Simple enough, right?
My first experience with Haskell and IO was with the typical "Hello, world!" program:
main = putStrLn "Hello, world!"
That seems simple, too. Surely it won't be too hard to do something like this:
player = new Player
main = while (alive player) (play game)
Well, that would be very hard for a beginner to do, considering that "player" is a constant (there are no variables in Haskell) and there are no loops in Haskell, unless you install "monad-loops" and use whileM, untilM and the like.
The preferred solution, I now know, is to write a recursive function that uses the State monad to pass along a Player data type. A beginner has no idea how to approach a problem in Haskell when their usual tools are not present.
Monads?
In imperative, "traditional" languages, there is no concept of the monad. In Haskell, it's everywhere, even the "Hello, world!" program was 100% monadic: the main function always has type "IO ()". The "IO" means we're in the IO monad.
While this would be a great time to have a terrible monad analogy tutorial, the easiest way of understanding monads, I think, is to come at them from a mathematical angle. I think I did this in that other post about functors here. A monad is just a container for a value with a few functions defined: "return" takes a value and puts it into a container, and "join" turns a value in a container in a container into a value in a container.
That's really simple, but also really useful. Most beginners are not exposed to anything like monads or functors, so they are naturally very hard to grasp.
Conclusion
Don't be discouraged because you find learning Haskell, Lisp, APL, Forth, or any other esoteric but useful langage hard. The only reason it seems difficult is that you're starting from scratch: it gets easier once you know the basics.