Hacker News new | comments | show | ask | jobs | submit login
Learn you a Haskell for great good (learnyouahaskell.com)
61 points by christofd on Mar 27, 2009 | hide | past | web | favorite | 24 comments

I like the material that's been posted so far, but the true test will be how he tries to handle a head-on discussion of Monads.

I've been checking up on it every so often since it was first released, and I just noticed he has a (crappy) feed now.

This was the first tutorial that I used to learn Haskell way back when. If you liked Why's Ruby tutorial then this might be good for you.

What I liked: 1) The Modules and Typeclasses sections 2) The operator tutorials, there are sections where they introduce all related operators, I like this method of introducing operators.

I did have 2 complaints with it. 1) Thought it's somewhat incomplete, missing the monad tutorial and 2) It's more of a small snippets on how to use the syntax, it doesn't teach by building programs that can solve real world problems. This may not be much of a problem depending on how you learn.

Though back when I read it RWH wasn't out yet so your choices were more limited back then.

I'm enjoying RWH (available freely at http://book.realworldhaskell.org/read/). I'm only about a third of the way through it, but every few chapters the author ostensibly takes a break from the pedagogy to show us how to build a real program (in reality, there is a lot of teaching in those chapters, so don't skip them).

I have a question for the hackers here. Im a beginner programmer (<1 year of real world programming experience so far). What is the kind of problems (web app in particular) that functional programming helps to resolve? And if i dont use myself all the features of OOP in Ruby, does it makes me a "functional programmer"? There must be some concepts that are unique to functional programming. What is the Essence of functional programming vs OOP programming? Why functional programming? (it would be nice for me to have a wikipedia page about this but in simple english).

Thanks in advance.

With regard to web apps, you ask a big question, which I, for one, am unprepared to address in a brief comment. In general, however, functional programming aims to make it easier to think about what a program does as it runs.

In imperative programming, you create variables and you change their values (or the values they reference). This is called mutable state. Functional programming claims that mutable state makes it difficult to reason about the value of a variable, because anything and anyone might have changed it. I happen to agree with this claim. Functional languages discourage, or outright forbid, changing variable values or referenced values. Instead, functional programs call functions in which the desired new value of the variable is an argument.

Initially, for people weaned on imperative languages, this is a mind-bender. It turns out, however, that with a little practice (I'd say less than two days of reading through SICP), that functional programs can be shorter and easier to understand than imperative programs. It turns out that modifying state becomes even trickier to think about once you introduce multiple threads of control. Variables clobbered by different threads leads to subtle and difficult bugs. A purely functional approach aims to eliminate this source of bugs at its root.

Kool-Aid-free answer:

"Functional programming" can mean one of three things.

1. Higher-order programming: the use of higher-order functions to make code more general/compact. Since you know Ruby, you're already doing some higher-order programming, but you may not understand it well enough to take full advantage of it. The best way to learn higher-order programming is to read SICP.

2. The use of immutable (aka persistent, nondestructive) data structures, i.e. data structures that can be updated without mutation. For example, linked lists allow you to prepend an element without changing the original list. This makes code cleaner in some cases and has advantages for concurrency. For specifics, look up Clojure.

3. Functional purity, aka referential transparency: the property that, for any input x, a function must always produce the same output x'. That is, all functions must be functions in the mathematical sense of the word. In practice, this doesn't really mean eliminating impure functions, but rather partitioning your program into "provably pure functions" and "possibly impure functions," using a clever hack (monads) to ensure that the output of possibly-impure functions is invisible to the provably-pure ones.

So, supposing you had a pure function whose behavior you wanted to randomize, you couldn't just throw in a call to the random() function, but would also have to convert it and all the functions that depend on it into impure (monadic) functions, replacing all function calls with the bind operator (>>=). It's quite complicated and I have never found a reason to believe it's a good idea, other than some theoretical stuff about "safety" and compiler optimizations that make your program 2% faster. I suggest not wasting your time on it.

You don't understand monads very well, they are orthogonal to 'impurity', and don't even necessarily involve state or sequencing at all.

A more straightforward way to describe how you do random() is that your function takes the randomness as an argument. Functions that take input take IO as an argument, and those that produce output return IO in some form. You don't even have to mention the dreaded word.

monads... don't even necessarily involve state or sequencing at all

I never said that they do.

You used the word "impurity". What other meaning did you intend?

Consider the getLine function:

  getLine :: IO String
getLine takes nothing as its input--not the state of the world, or whatever, but nothing--and produces a string that's inside an IO monad. To all appearances, it's an impure function. But because the String is inside a box (so to speak) you can't peek at it--instead, you can only give it to another function, provided that function also returns a box you can't peek into.

In effect, this creates two classes of functions. Some, like getLine, can have side effects, generate random values, look into other boxes, whatever. These are technically "pure" in a mathematical sense since their outputs are always inside identical boxes, but in practice they behave just like impure functions, and you reason about them the same way. The rest have more restricted capabilities, and because their values aren't inside boxes, the compiler can verify that they return distinct values in a pure manner.

So, for an extreme example, consider this function:

  foo (x) { x++; return x; }
In Haskell foo would return "IO Int," which means the "Int" may be produced impurely. Actually, the integer is produced in a pure manner, so it would be fine for foo to return a plain Int. But the way Haskell determines purity is too crude to figure that out: all it knows is that foo depends on a mutation, which may make the output impure. Impurity is contagious, so foo "catches" it from ++ and thus has to put its output in a box.

Hence, the typical description of pure functional programming as "functions only transform inputs to outputs" is only true in the most trivial mathematical sense, and has no practical import. Pure functional programming really means writing a crude proof that a certain subset of your program is pure, and the compiler checking the proof for you.

(Disclaimer: I understand that this is a very vague high-level description of monads, that monads can work in other ways and do many other things, and that purely functional languages can handle state/side effects/etc. in other ways. It's a complex issue and I'm just trying to convey the way that it usually works in practice.)

I think you are conflating the IO monad with monads in general. For example a stateful idom like x++ in your foo example could be simulated with a state monad, but that does not have anything to do with IO, and would certainly not require the IO monad as you seem to suggest.

You can encapsulate the use of a state monad so you can indeed return a pure int even if you use a state monad inside the function. It is only the IO monad which (for obvious reasons) cannot be encapsulated. So only the IO monad is "contagious" in the way you describe. This is only a problem for you if you have input and output spread all over your program.

I don't really agree with your treatment of monads, but perhaps I don't understand your point of view.

What did you mean by "you can't peek at it"? I can peek into the String returned by getLine in pure code, as in the following example:

  myStr <- getLine
  let len = length myStr
  print len
Here, myStr is of type String and 'length' is a pure function of type [anything] -> Int. The <- ("gets") syntax unwraps the IO box around the result of getLine (which is of type IO String, as you noted). Once it's unwrapped, we can treat it as a purely functional value and call length on it. Then I can pass it to the 'print' function, which is also in the IO monad. The compiler will ensure that 'length' is pure.

I also don't understand what you mean when you say getLine "takes nothing as its input". Since it's in the IO monad, it implicitly takes the "RealWorld" as its input, updates it, and implicitly returns a new RealWorld. I prefer this treatment of the IO monad, because it's easy to see how _every function_ is pure in Haskell -- it's just a matter of giving it different (implicit or explicit) arguments.

To be fair, the RealWorld analogy is not exactly how the system works. But it is the abstraction that the Haskell language likes to make (from the docs: "RealWorld is deeply magical. It is primitive, but it is not unlifted (hence ptrArg). We never manipulate values of type RealWorld; it's only used in the type system, to parameterise State#.")

Anyway, an example which is actually purely functional but uses mutation:

  import Control.Monad.State.Lazy
  incr :: State Int ()
  incr = modify (+1)
  add2 :: Int -> Int
  add2 val = execState incrTwice val 
        incrTwice = do
  main :: IO ()
  main = do
        print (add2 5)
This program prints 7.

The 'incr' function here, like the getLine function, accepts no explicit arguments. However, it is in the State monad, parameterized with an Int, meaning that it implicitly accepts a mutable Int which it can modify. In this case, it adds one to that int, and doesn't return anything (that's the () data type, pronounced "unit").

The 'add2' function here is purely functional: it has type Int -> Int. And yet it depends on mutation occurring because it calls incr (twice). The execState "creates" a State monad from scratch and gives it an initial state (val), and then runs the given State action (incrTwice).

The main difference between the functional State monad and IO (which -- awesomely -- is actually defined in the source code as a State of RealWorld) is that you can't create instances of RealWorld, so you can't execState an IO action. In order to run an IO action, you have to get the RealWorld from the only place it enters your program, the main function.

Did I clarify anything, or did I misunderstand you?

Ok, you're right. That is a better explanation of monads than the one I gave.

I intended my example to represent cases where you want real mutation, in the sense of modifying a data structure in place. Your code using the State monad, if I understand it correctly, just abstracts functional updates in a way that looks like mutation.

Anyway, my intent in my original comment was simply to explain the practical consequences of pure functional programming. That, for instance, if you have a pure function that you want to randomize, this change must be reflected in all the code that depends on it. My intuition is that this is a bad thing, but I don't have enough experience to say for sure. However, it is apparent to me that purity

1. adds a lot of complexity to the language, and

2. has no clear practical benefits,

which is why I say it's a waste of time.

I think web apps are an interesting thing to try at least with FP - in a sense its functional: response = f(request).

You certainly can do FP style in most languages, but when you at least play with something that is more a pure FP (haskell is great and beautiful) it really makes sense when you see those concepts applied elsewhere - an interesting way to use haskell is Happs: http://dbpatterson.com/articles/5 (the haskell app server).

Just make sure its fun !

I agree that functional programming and web applications fit nicely together. response = f(request) probably won't mean much to someone who has no familiarity with fp, though. http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch... is a key section of a very readable explanation of web architecture. I think it could serve as a very practical, if unconventional, demonstration of the usefulness of referential transparency.

yes I guess I made the assumption of some level of maths - but perhaps its not common for people starting out now (I know it took me years before I appreciated all the mathematical stuff).


Uses a Java-esque language for code samples, and required reading for my new hires. This and others is part of a collection of introductory-but-mind-expanding articles.

> And if i dont use myself all the features of OOP in Ruby, does it makes me a "functional programmer"?

No, that would be imperative procedural programming. (You are doing functional programming if your program flow can be described as a single sequence of function applications. This is hard in Ruby.)

The rest of your questions are well-answered throughout the Internet.

Basically funtional programming helps resolve the problem of bugs. If you rarely make bugs in your code, you probably wont get any advantage from functional languages. They don't allow you to do anything practical which is not possible in OO or imperative languages (and vice versa).

So, how does functional programming reduce bugs? The theory is that we make bugs because programs are too complex to grasp fully (even if we wrote the whole progam ourselves), which makes us make wrong assumptions about what happens in a given piece of code, and how that piece of code will influence other parts of the program.

Functional programming tries to reduce the amount of different "invisible" outside factors which may determine what will happen in a given piece of code. This makes it easier to understand code and avoid mistakes.

You should read Structure and Interpretation of Computer programs. It is available in french at Dunod iirc.

If Haskell is good for webpages then I may give it a try, if not I´ll pass.

If it is, please somebody show us how to spit html and from there we´ll pick it up. No better-than-sex monads, no intergalactic scripting services, no functional masturbation.

I really want to know if it is going to save me time and resources for my next startup, nothing else.

I really want to know if it is going to save me time and resources for my next startup, nothing else.

You should probably learn to program before starting a software company.

If it is, please somebody show us how to spit html and from there we´ll pick it up. No better-than-sex monads, no intergalactic scripting services, no functional masturbation.

Anyway, to print HTML in Haskell, try this:

    putStrLn "<html><head><title>Hello, world</title></head><body>Hello, world!</body></html>"
Amazing, although I'm not sure what you gain by doing this.

(As an aside, I prefer HXT to generate XHTML programatically. It doesn't involve "better-than-sex monads", either, although it does involve Arrows.)

I wish I could resist the urge to reply to trolls, but apparently I can't.

Hmm.. in my case, part of the fun of learning Scheme was that it had an excellent article on how to generate html web pages... and the syntax was nice [like arc and ruby]. Same for arc, but its newer so lacks the many pre-configured libraries.

Haskell is a nice language, its just a matter of time until someone writes a web tutorial like that, than a web framework etc etc.

But Haskell might be a bit more of a mathematically pure / sophisticated language.. you'd need to commit to some study I think to master it - whereas with ruby [or python] you can sort of hack in a php style, and learn nicer things later on.

Ive seen good blog discussions that 'Ruby is a good enough lisp' and it sort of is to a first order.

So don't sweat it, just use ruby or python or scheme.. or Haskell if it resonates with you. They're all so much more enjoyable than Java or C++ :]

I've spent some time with Haskell and I like it well enough, but: In all seriousness, if you don't already know Haskell, I would not recommend using it in a web startup. That's not a time to be learning Haskell. Go with something you know, or, failing that, one of the Python frameworks.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact