I've read the Learn You a Haskell book up to the point where they introduce Monads and stuff like Just a. These are so counterintuitive for me that I just feel like giving up trying to learn it.

But I really want to try.

I'm wondering if I can at least avoid the advanced concepts for a while and just starting using the other parts of the language to do a lot of practical tasks with the more basic parts of Haskell, i.e. functions, standard IO, file handling, database interfacing and the like.

Is this a reasonable approach to learning how to use Haskell?

  • 2
    I don't know how far you'll get. Without monads you can't do anything useful in Haskell, since doing anything useful in programming (as in, writing a serious program that someone would actually want to use) requires I/O and state, both of which can only be managed in Haskell through the use of monads. – Mason Wheeler Jul 26 '11 at 3:31
  • 1
    @dan - I finally understood Haskell monads from reading two documents - "You could have invented monads" and "Tackling the Awkward Squad". I can't say whether they're better than "Learn You a Haskell", which I haven't read, but they worked for me. To use the do notation with the IO monad, you can get by with very little understanding of what monads are - you'll do some things that will make the experts either laugh or cry, but superficially at least, it's not far different from using a conventional imperative language. But it is worthwhile getting some deeper understanding. – Steve314 Jul 26 '11 at 22:42
  • 2
    @dan, it took me nearly a decade to get bored of OOP and start to appreciate / be curious about functional languages. I would still learn F# and Clojure before I give Haskell a serious try. While personal challenges are nice, there is something to be said about your gut feeling and the path of least resistance. A lot depends on who you are. If you are a strongly mathy type, then you would probably like Haskell/Schem right from the start. If you are more of a 'get it done' guy, it is ok too. Do not approach Haskell with a 'do or die' attitude. Go on learn other things, and when bored come back. – Job Jul 27 '11 at 2:38
  • Thanks for the advice @Job. I have tried Clojure for a bit, but the Lisp syntax really does not work for me, for both reading and typing. I find Haskell's syntax more natural. I'm happy using Ruby for projects in the meantime, but I hope to start doing some practical projects in Haskell. – dan Jul 27 '11 at 14:35
  • @dan, Funny, I find Clojure's syntax rather friendly, but that is probably because I was exposed to Scheme before. Maybe after F# I will get used to the way Haskell does things. – Job Jul 27 '11 at 15:23

Let's first distinguish between learning the abstract concepts and learning specific examples of them.

You're not going to get very far ignoring all the specific examples, for the simple reason that they're utterly ubiquitous. In fact, the abstractions exist in large part because they unify the things you would be doing anyway with the specific examples.

The abstractions themselves, on the other hand, are certainly useful, but they're not immediately necessary. You can get pretty far ignoring the abstractions entirely and just using the various types directly. You'll want to understand them eventually, but you can always come back to it later. In fact, I can almost guarantee that if you do that, when you do get back to it you'll slap your forehead and wonder why you spent all that time doing things the hard way instead of using the convenient general-purpose tools.

Take Maybe a as an example. It's just a data type:

data Maybe a = Just a | Nothing

It's all but self-documenting; it's an optional value. Either you have "just" something of type a, or you have nothing. Say you have a lookup function of some sort, that returns Maybe String to represent looking up a String value that may not be present. So you pattern match on the value to see which it is:

case lookupFunc key of
    Just val -> ...
    Nothing  -> ...

That's all!

Really, there's nothing else you need. No Functors or Monads or anything else. Those express common ways of using Maybe a values... but they're just idioms, "design patterns", whatever you might want to call it.

The one place you really can't avoid it completely is with IO, but that's a mysterious black box anyway, so it's not worth trying to understand what it means as a Monad or whatever.

In fact, here's a cheat sheet for all you really need to know about IO for now:

  • If something has a type IO a, that means it's a procedure that does something and spits out an a value.

  • When you have a block of code using the do notation, writing something like this:

    do -- ...
       inp <- getLine
       -- etc...

    ...means to execute the procedure to the right of the <-, and assign the result to the name on the left.

  • Whereas if you have something like this:

    do -- ...
       let x = [foo, bar]
       -- etc...

    ...it means to assign the value of the plain expression (not a procedure) to the right of the = to the name on the left.

  • If you put something there without assigning a value, like this:

    do putStrLn "blah blah, fishcakes"

    ...it means to execute a procedure and ignore anything it returns. Some procedures have the type IO ()--the () type is sort of a placeholder that doesn't say anything, so that just means the procedure does something and doesn't return a value. Sort of like a void function in other languages.

  • Executing the same procedure more than once can give different results; that's kind of the idea. This is why there's no way to "remove" the IO from a value, because something in IO isn't a value, it's a procedure to get a value.

  • The last line in a do block must be a plain procedure with no assignment, where the return value of that procedure becomes the return value for the entire block. If you want the return value to use some value already assigned, the return function takes a plain value and gives you a no-op procedure that returns that value.

  • Other than that, there's nothing special about IO; these procedures are actually plain values themselves, and you can pass them around and combine them in different ways. It's only when they're executed in a do block called somewhere by main that they do anything.

So, in something like this utterly boring, stereotypical example program:

hello = do putStrLn "What's your name?"
           name <- getLine
           let msg = "Hi, " ++ name ++ "!"
           putStrLn msg
           return name

...you can read it off just like an imperative program. We're defining a procedure named hello. When executed, first it executes a procedure to print a message asking your name; next it executes a procedure that reads a line of input, and assigns the result to name; then it assigns an expression to the name msg; then it prints the message; then it returns the user's name as the result of the whole block. Since name is a String, this means that hello is procedure that returns a String, so it has type IO String. And now you can execute this procedure elsewhere, just like it executes getLine.

Pfff, monads. Who needs 'em?

  • 2
    @dan: You're very welcome! If you have any specific questions along the way don't hesitate to ask over on Stack Overflow. The [haskell] tag there is usually pretty active, and if you explain where you're coming from people are pretty good at helping you gain understanding rather than just tossing cryptic answers. – C. A. McCann Jul 26 '11 at 21:07

How important are Haskell's advanced concepts like Monads and Applicative Functors for most routine programming tasks?

They are essential. Without them, it's all too easy to use Haskell as just another imperative language, and although Simon Peyton Jones once called Haskell, "the world's finest imperative programming language", you're still missing out on the best part.

Monads, Monad Transforms, Monoids, Functors, Applicative Functors, Contravarient Functors, Arrows, Categories, etc, are design patterns. Once you fit the specific thing that your making into one of them, you are able to draw on a huge wealth of functions that are written abstractly. These type-classes are not simply there to confuse you, they're there to make your life easier and to make you more productive. They are, in my opinion, the biggest reason for the conciseness of good Haskell code.

  • 1
    +1: Thanks for pointing out that "Monads, Monad Transforms, Monoids, Functors, Applicative Functors, Contravarient Functors, Arrows, Categories, etc, are design patterns." ! – Giorgio Aug 13 '12 at 11:37

Is it strictly necessary if you just want to get something running? No.

Is it necessary if you want to write beautiful idiomatic Haskell programs? Absolutely.

When programming in Haskell it helps to keep two things in mind:

  1. The type system is there to help you. If you compose your program out of highly polymorphic typeclass-heavy code, very often you can get into the wonderful situation where the type of the function you want will guide you to the (one single!) correct implementation.

  2. The various available libraries were developed using principle #1.

So when writing a program in Haskell, I start by writing the types. Then I start over and write down some more general types (and if they can be instances of existing typeclasses, all the better). Then I write some functions that get me values of the types that I want. (Then I play with them in ghci and maybe write some quickCheck tests.) And finally I glue it all together in main.

It's possible to write ugly Haskell that just gets something running. But there is so much more to learn once you've reached that stage.

Good luck!

  • 1
    Thank you! I've been wavering between Clojure and Haskell, but now I am leaning toward Haskell because of how helpful people like you have been. – dan Jul 26 '11 at 23:43
  • 1
    The Haskell community seems to be very nice and helpful. And it seems to be where a lot of interesting ideas come from – Zachary K Jan 15 '12 at 9:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.