John Goerzen is a co-author of Real World Haskell. In a recent interview with O'Reilly, he explained how the book came to be, the special magic which makes Haskell worth learning, and how to change your mindset to make learning possible.
One of Haskell's benefits is that you can use it as a purely functional language -- but that's really different for people who've come up in the imperative or object-oriented worlds. What does it take to learn how to think in a pure fashion?
Goerzen: That's probably the single biggest mind-shift that you deal with coming from a different language; that and laziness. Both of those are both pretty big.
As for how to learn about it, it's a lot of relearning how to do some very
basic things and then building upon that. For instance, in imperative
languages, you have
for loops and you have
loops. There's a lot of having a variable there and then incrementing it as
you iterate over something. In Haskell, you tend to take a recursive approach
rather than that. It can be a little bit scary at first because you might be
thinking if you're using a language such as C, incrementing a variable is a
pretty cheap operation.
Obviously higher level languages tend to not be quite as fast as C, but the GHC compiler that we tend to use is pretty efficient at optimizing what you express in recursive terms into a format that's fairly efficient for the CPU.
Does GHC rewrite from recursion into iteration?
Goerzen: Yes. Brian or Don know more about that process than I do. Once you get past that initial hump of thinking that this might be kind of a scary thing performance-wise and dive in.... When I learned it, I found it to be a very powerful way of thinking about things because it really eliminates almost all possible side-effects out of your code.
That's one of the big things that you deal with in just about any language. If you call a certain function and it doesn't do what you expect it to do, maybe it's got a bug; maybe its documentation isn't right or something and it changes it the stage of the world. In Haskell, the things that can possibly change the state of the world are tightly isolated.
They're marked as such, and enforced by the type system.
Goerzen: Yes, that's right. In Haskell, the parts of your program that are impure are marked with the type system kind of like Java marks exceptions. It's a little bit of a different approach, but it leaves me with this warm fuzzy feeling that the code that I push out into production is pretty correct. There can still be bugs in Haskell code, but I feel more confident about the code that I write than I tend to be with some of the other code that I've written.
Is it more likely that the remaining bugs are bugs in specification and understanding than bugs in implementation?
Goerzen: Yes, probably so. Part of that is that due to the type system where Haskell can be used as a very agile language like somebody would use Python, but yet, it has static type checking. A whole class of errors that might be caught only at runtime in certain other languages can be caught at compile time at Haskell. There's another thing that plays into it, but I think you're exactly right. It would be logic errors or specification errors that would trip you up more often in Haskell.
Has anyone used the phrase "checked purity", like Java's "checked exceptions"? If they haven't, I want to claim it.
Goerzen: [Chuckles] I haven't heard that term.
When you mentioned recursion, I thought about pattern matching. I realized that I could express recursive conditions, my termination clauses and my exceptional conditions very, very succinctly in Haskell. That was a revelation.
Goerzen: In a way, it seems like if you look at at least simple Haskell functions, they express things maybe not the way that most people would think about it if they've been programming it in imperative language for a while. But it's a very simple way to state a problem. For instance, if you're writing a recursive function that does something to every element of the list, you say, "Okay. This function on an empty list, the result is an empty list or zero or whatever....
The identity function or whatever.
Goerzen: Exactly right. To me, it seems like it makes it easy to reason about the logic that you can clearly see. In this particular case we do this; in this particular case we do that. You're right, it's succinct to write, but that doesn't come at the expense of readability.
I don't care for Haskell's syntax in general, but I like the expressiveness of pattern matches. I also really, really like how purity lets you test individual functions in isolation. That gives me confidence that my functions are composable without errors or mismatches.
Goerzen: Yes, the notion of purity enables a lot of things and testing is one of them. Have you read much about QuickCheck already?
I know of it, but I've not used it in Haskell.
Goerzen: I've used it some. I haven't used it a whole lot, but that is a really, really powerful way to go. It's not appropriate for everything -- say when I write a tool that maybe goes out and downloads podcasts or something, obviously there's some IO going on there and you can't test what some remote server's going to return to you with QuickCheck. But you can test a whole lot of things and especially if you structure a program in terms of building your program around a lot of small functions and then combining them together. It's pretty easy to point QuickCheck at a lot of those small functions.
Haskell reminds me of Forth and other concatenative languages in that way. I hadn't really made that connection before, but that's a nice thing. The Lisp people might say the same thing. You build a vocabulary of functions or words and then you write your solution in the language of that vocabulary.
Goerzen: One of the phrases I like to use is that in Haskell, we manipulate functions as easily as Perl manipulates strings. To a certain extent, it really is true. In any language you can write small functions and combine them to assemble your whole, but in Haskell, you can really slice and dice a lot more than you'd tend to do in some other languages.
First-class functions are a huge benefit. More than that -- and I have a love-hate relationship with this -- is invisible partial application and currying. I hate that it's invisible, but I like having it there. I think, "Oh, if I just knew the arity!" That's a love-hate thing.
Where does laziness help real world programs? The traditional explanations I've seen in beginning tutorials and calculate Fibonacci, which is not that interesting.
Goerzen: One of the first things that I thought was
interesting was how laziness relates to IO in Haskell. In Haskell there's a
a string. That string represents the entire contents of standard input or of a
file or whatever, but it doesn't just read the whole thing into memory all at
once. It also doesn't
mmap. In other languages, if you want a
string that represents an entire file, you're either going to
the thing or you're going to have to waste a lot of memory, or you read it in
blocks or lines or whatever else you might do. If I write a little filter or
parser program in Haskell, I don't have to really worry about the details of
reading the input line-by-line or block-by-block because I can just use
getContents and then I can string together a whole bunch of
functions. I like writing Unix filters in Haskell.
The concept is kind of like writing a filter in a shell. You string together a bunch of functions and you pipe the output from one into the next.
I had a discussion with Peter Weinberger and Brian Kernighan about Unix culture. Someone explained Unix pipes to me as function composition. They said, "Oh, yeah. We had that in mind from the beginning."
Goerzen: That's very interesting. You have the laziness
operating on your input. That means that you can say
and it returns a string, but it doesn't actually read anything or it doesn't
actually read much until some computation actually requires access to the
contents of that string.
The read doesn't happen until you try to observe it.
Goerzen: Exactly right. If you're writing a filter in
Haskell, if for whatever reason you left of this
Proving that IO is your bottleneck because you finished the program.
Goerzen: That's right. Once you have the laziness on the input, you have the laziness on the output because printing something doesn't require it all to be evaluated all at once. Then basically what happens is as long as you don't require that the first part of the string stay in memory for some reason, then as you work through the file, the garbage collector will take care of getting rid of the data that was read three megabytes ago or whatever.
You wind up with a program that is a very simple design because you're not having to deal with buffering or iterating over lines or what not and yet, runs in constant space.
Is this a function of Haskell's pervasive laziness or the fact that it provids good library support that takes care of this buffering for you?
Goerzen: It's the laziness. I mean there is some library support for doing this sort of thing. For instance, I wrote one of the database layers that people use in Haskell. There's support for reading rows back from a database lazily. There's stuff in the library that enables people to write code to do this sort of thing. Writing code to do something like reading the actual database level stuff of reading rows from the database lazily is probably not something that somebody that's new to Haskell's going to do. But somebody that's new to Haskell can take advantage of it.
It's pervasive in the language; you need to do a lot of work to get away from it if you want to. If you follow the language as it is, the idioms as they are, you get this pretty much magically.
Goerzen: Yeah. Laziness is throughout the entire language. It can be really powerful, but it does require a mind-shift. If you forget that laziness is there.... When I was first learning Haskell, sometimes I'd write these programs that would use an awful lot of memory because it's just such a big shift that it's hard to remember that things aren't going to be happening quite like I'm used to them happening. Once you're used to it, you can really exploit it.
It seems like on one side, you're trading the ability to reason about types and purity and such for the inability to reason about time and space complexity. That may not be entirely fair.
Goerzen: I don't know that it's an inability to reason
about it. It's just that you have to understand that it works differently. I
can look at a Haskell program and I can look at it perfectly easily, and I can
say, "Okay. This program uses
getContents and then it converts
the entire file to upper case and prints out that. Then at the end, it prints
out the length of the entire file using a simple
You have to keep the entire thing in memory because it can't be garbage
collected until it calls
read. You'd open a file; you could call
read, and it could read an entire file for you into memory if you like. Some people would do this expecting that maybe they're only dealing with files that are 100K or something. You try to give them a file that's 2 gig and that approach doesn't quite work anymore.
It gets slow; then it stops.
Goerzen: Then maybe the entire machine stops.
How does someone learn to think in the Haskell way? Is it a matter of experimenting, or is there a mind-set shift that people need to keep in their heads as they're learning?
Goerzen: For me, it was just immersing myself in the language I guess and trying things and asking for help when I didn't understand why things weren't working the way I expected to. Haskell is probably not a language that somebody -- especially somebody that's not used to functional programming -- can just sit down and read a few chapters out of a book and be good at, like a language like Python, if you're already familiar with Perl. There are going to be some things here or there like its object system is very different from the object system in Perl.
Actually, it's the same. Honestly. There are some quirks in Perl -- the degree to which you have references exposed at the syntax level is one important difference, but otherwise, it's basically the same object system with different syntax.
Goerzen: It's definitely more than that. I learned Haskell because I had been programming primarily in Python for a number of years. I used a lot of Perl before that, and I wanted to learn something different. I picked up Ocaml and it was some different, but it had some library issues that really annoyed me. Then I thought I'd try Haskell, and it was very different. I liked it. I started using it because I wanted a mind-opening experience, and I got it. But it turned out to be useful too. I think that even if somebody doesn't wind up using Haskell every day that learning what's out there is always something that's good.
For instance, I don't use Java everyday, but I have programmed in Java and am familiar with it and understand the concepts out there. A lot of what's been done in Haskell sometimes tends to percolate into other languages. Python is one that especially has drawn things out of Haskell such as list comprehensions.
As you were learning Haskell, did you find that a lot of learning Haskell idioms is doing something the hard way and then having someone tell you, "Hey, dummy, there's a Prelude function that does just that"?
Goerzen: There was some of that. Probably not a lot of it,
but some of the functions in Haskell were just named with names that I didn't
quite know what to search for. There's
isSuffixOf that basically will take two
lists and say "Does list A begin with list B?"
Some of those things that I had been used to having different names in Python and either weren't in Haskell yet or weren't in Haskell with a name or a search term that I knew to use. [There's] just a little bit of a learning curve on what are things called in Haskell -- but that got better over time, and I started learning which module to look in for things. One of the things about Haskell is that the types of functions tell you an awful lot about what they do.
Once you learn how to read them.
Goerzen: Yes. I'm to the point now that if I'm looking for a function, I'll think about what type I expect it to be and then scan the list of functions at the top of the online documentation for the modules. My eye will [catch] functions that look like they're the type that I'm expecting.
That sounds like a multi-dispatch algorithm.
Goerzen: It's a little bit different way of referencing documentation I guess.
Do you think some of the jargon is still a little inscrutable? I understand what a fold is, but when you say "catamorphism", I start getting lost.
Goerzen: Me too. I don't know what that is either. I've been using [Haskell] now for a few years. This is the first language ever that I have learned that after using it as long as I have, I still feel like there is a lot out there about it that I don't know yet.
That's one of the reasons why it's so great to work with Brian and Don on this project. We have strengths in different areas and they really complement each other well. Don is a PhD student in Computer Science. He knows a lot about the theoretical underpinnings of the language -- things that I really don't know much about.
On the other hand, I wrote the database layer that most people are using these days.
There's a practical component there.
Goerzen: It worked out pretty well. But there are some concepts that are of fundamental to working in Haskell: maps, filters, folds. Some of that stuff you don't have to use, but it's such a common idiom that it will save you a lot of time if you know how to use it. They're all generalizing some certain typical recursive patterns.
Probably the number one thing that I've heard that scares people away are monads. I understand why people are scared away. In fact, I was for a while too. But you can write a perfectly good Haskell program that deals with IO without having to be terribly bothered by what monads are or what they do. That's kind of how we present it in the book. It's Chapter 7 we start talking about IO and the separation between pure and impure code -- and that there is this thing called the IO monad. In order to understand IO, you don't have to understand monads. For now, you can just understand it as this kind of box that separates out the pure from the impure code.
Later on in the book, we go into more detail about what monads really are and what else they can do for you.
There are only three monad functions anyway.
Goerzen: On the one hand, it's a very simple concept. On the other hand, it's almost so powerful that it becomes kind of complicated. That's kind of an odd way to describe it, I guess, but there's an awful lot that you can do with monads. For instance, we talk about monads used for threading computations through or threading data through computations. They can be used to easily do things like if you're performing a whole series of computations and need to stop if there's an error and return some error code.
You're thinking of
Goerzen: Or the
Maybe is a simple way to do it, but that approach can be
generalized to use
Either. For instance, you could return either a
return value or you could return an error value if you need to say something
more nuanced than just it failed type of thing.
That's just a simple place where monads can be useful. In the IO cases,
it's a monad with this opaque type that wants something.
kind of the roach motel of Haskell; when something checks in, it doesn't check
It's the Rubicon of pure functional languages.
Goerzen: There you go. Cross the river and you're not turning back.
Per my understanding, Haskell didn't get monads and wasn't really about to do IO from 1990 until '98. It seems very academic to have a language that likes to design in purity but is practically useless for just about every program ever written.
Goerzen: Yes, isn't that odd? That's one of the reasons why we wrote this book. I was trying to learn Haskell and -- I don't know if you know, but I work for a company that makes lawnmowers. All the programs that I write involve IO. I'm not sitting there at an interpreter. I can understand how it could be useful in an academic setting. You could load things into an interpreter and put whatever you're working on. I'd read some of these Haskell tutorials online or even some of the books and IO is the last chapter or an appendix, or it's this thing that people think either isn't useful or is scary or is the heathen part of the language or something. I don't really know. But I don't think that IO in Haskell is bad. In fact, Haskell's IO system is really quite powerful. I don't think it's difficult to learn if you have somebody that presents it right.
It was frustrating for me coming from my background to go through these tutorials. I'd be going through chapters of them m thinking, "Boy, I just want to compile a program already and run it and make it do something and be able to play with it."
I can write a Unix filter in four lines of shell.
Goerzen: Yes. Right. Exactly.
How did you decide to write Real World Haskell? The title describes your approach very, very well.
Goerzen: To be quite honest, I don't remember where the title came from. That was a year-and-a-half, two years ago. Brian and Don were talking about this project before. I was the third one to join the team. I can't remember if they had already had the title in mind or not. But our approach is I guess you might say the typical O'Reilly approach. We wanted to be heavy on useful examples in the book.
Admittedly, there are some contrived examples in the book because sometimes a useful example is too complicated or something. But we have a number of things in there that are significant full programs or significant chunks of a full useful program. We have a program that will take a graphic -- a photo of a barcode -- and then spit out what the value of that barcode is. We've got the podcast aggregator that then in a couple of chapters we add a GUI onto it using the GTK binding for Haskell.
Things real people might actually really want to do with a real programming language.
Goerzen: Exactly. We try to introduce things with real code, with real examples, and with tips that are geared to somebody that's using this in a business environment or a shell filter environment,-- something that people tend to write real programs with. [We] then build upon that as we go through succeeding chapters, again, trying to provide as many examples as possible. It provides people with something that's useful to play with.
A lot of people learn better not just by reading but also by doing. If we can talk to them about how it works and also show them how it works and give them something they can play with and hack on and tweak, that's a good way to help people deal with this big mindset shift.
Can this cross over from language dilletantes to mainstream groups?
Goerzen: I would hope it would cross over into more mainstream groups. I know that Haskell is used in places like investment banks.
They hire quantum physicists. That's not your average programmer.
Goerzen: My next sentence was going to be but they may be using these top-tier geeks and techies also. I don't know. It's still at this early stage of coming out of the academic world and being recognized more widely. Even just since we've started this project, we've seen a continued breakout of that shell more.
You had 750 people comment on the previews.
Goerzen: That's right. That whole process was one of the most incredible tech review processes ever. Right now, you'll probably see more of the geeks learning Haskell. There are people that do programming because they love to do programming. Then there's people that it's a job. I don't think it's quite to the point where people in the latter category are going to pick up Haskell because it's interesting, but I've seen a lot of geeks that are definitely interested. It may be not necessarily they're going to drop everything else and shift to it, but it's something that' s different and interesting and worth learning some about anyhow.
Do you believe into Simon's philosophy of "Avoid success at all costs"?
Goerzen: Yes. And at that, we've been kind of a failure lately, haven't we?
I think I told him the same thing. You're having problems because you didn't avoid success.
Goerzen: It's definitely a new place. The Haskell community is definitely going places it's never been before. So far, I think it's been a very good experience for everybody. One of the funny things on being on some of the Haskell mailing lists is just the vast number of extremely intelligent and extremely helpful people there are over there.
Those people aren't going away, at least not yet. It's not like there's been this influx of interest from business and other communities, but it hasn't driven away the academic people either.
You can look at these Haskell mailing list discussions and see everything from obscure discussions on type theory or computer science concepts that post-graduate people are working on -- and everything from that over to version control systems and Unicode and coding libraries, databases, Unix shell libraries. It really spans a wide range. I rather suspect that very few if any people really understand the whole range of it.
That is a good sign of success, when people are using software you created for niches and ideas you never anticipated -- as long as they're not evil uses or not primarily evil.
Goerzen: Yes. Will we truly have a successful language when spammers are using it?
I think Perl is a wild success there.
Goerzen: There you go.
Sometimes I'm a little bit sorry I helped. Haskell's probably more like super villains taking over the east coast -- and investment banks, what's the difference these days?
Goerzen: That's right. That's right.
Do you have any follow-ups or final things to say to our audience?
Goerzen: Oh, one thing I might touch on you touched on just briefly was the process of writing this. This was a different process than I had ever been involved with before. I had written a few books before, and I don't know how much everybody is aware of what we did, but we basically put the entire book online under a Creative Commons license and put a little comment box by every paragraph. Like you said, we had hundreds of people that left us comments. We just got an awful lot of comments, and it was a very helpful thing for us as authors. It was a lot of fun to see.
At one point I compared a particular feature in Haskell to a particular feature in Java. Somebody jumped on my case legitimately and said, "That's not a good comparison." A couple other people would reply to that. Eventually it got into this discussion, "Well, should he be comparing it to Lisp instead or maybe to Python or Perl?" -- this whole argument over one sentence in the book, and that's the level of engagement that people in the community had in this. I think it helped us to make a much better book than we could've had otherwise if we had just had a handful of tech reviewers in the conventional way looking at it.
To see people's enthusiasm has really been a humbling experience for me. I really appreciate all of the time that those people contributed to the project.