After a couple of aborted attempts in the past, I’m learning
Haskell in earnest now. I think it’s
because I’m just tired of feeling stupid. I hang around programming.reddit.com quite a bit and Haskell is a big
topic of conversation there. In fact, I
can’t remember the last time I visited without reading some little bit of
proselytizing or some effusive post about the productivity of the language. I’m not knocking that; I’m all for it
actually. The thing is, I was tired of
not really knowing what a monad was, or how to really go about programming
anything substantial without side effects, and it was getting to me; so off I
went in the stray moments around Christmas vacation with my old dog-eared copy
of The Haskell School of Expression. And, like anyone who has been programming in
a completely different paradigm for years, I started to try to make sense of
what I was learning in terms of what I know.
For the most part, it’s been a good ride. It took me a little while to figure out that when a Haskeller declares an algebraic data type, the constructors are, in a sense, like the states of an object in an OO language. In the Haskell School of Expression, Paul Hudak uses a running example of a program manipulating shapes. He declares a Shape data type with constructors for Square, Rectangle, Circle, etc. All the while, he starts to declare functions which operate on values constructed via each of these constructors. In the back of my mind, of course, I was screaming what connects all of these things? In OO, you’d have common shared behavior but here you have to treat each construction separately. Eventually, when I started looking at recursive data types, it made more sense.
The other hurdle I’ve been getting over is the Haskell
approach to naming. An example: there is
a function in the standard prelude called foldr1. What that means, for the uninitiated, is:
perform a list fold operation from the
right to the left and use the last elements in the list as the basis of the
fold. Now, I don’t know how to make a
better name than that for what foldr1 does, but it’s hard not to feel bad about
it. Names should really
communicate. Yes, in C we had names like
sscanf and fprintf but, when those names were settled, compilers and linkers
only cared about so many characters, right?
From what I’ve seen so far, this approach to naming is pretty common in Haskell. In Simon Thompson’s book Haskell: The Craft of Functional Programming, he starts with an example that has two functions: flipV and flipH. They could’ve been named flipVertical and flipHorizontal, but it seems that the Haskell way is to use abbreviations and short identifiers. It’s great when you already know what the function names mean, but if you don’t you have to do a bit of hunting around to decipher code.
I’ve been thinking a bit about why Haskellers approach
naming this way, and I have a couple of ideas. One is that Haskell came, essentially, from the computer science
community. Most Haskell tutorials and
books make more than passing mention that Haskell programs are easy to reason
about and can serve as launching points for proofs of correctness. Haskell also looks a lot like math. List comprehensions look very much like set
notation. If you have symbol y you can have
distinct symbols named y’ and y’’. It’s
cool and mathematical; you can almost hear the chalk on the chalkboard.
I think that culture does have a lot to do with Haskell’s naming conventions, and I’m sure that my own culture has a lot to do with my concern over them. Many OO practitioners go out of their way to make clear evocative names. When you write code that way, it reads like a story.
If I say account.deposit(2)
in an OO program, or account.getBalance(),
I’m stringing together nouns and verbs. Yes, it’s backwards, of course. In English we put verbs before the things that they operate on, so you’d
think that in Haskell, things would be better. We could say getTail [1, 2, 3],
for instance, to get the tail of a list, but the naming conventions
don’t quite work that way. If you want
the tail, you say tail [1, 2, 3]. In this case, the function is given a noun
name; it’s named after its result.
I first saw this “name the function after its result” style of naming in a textbook about functional programming years ago. I forget which book it was. It’s nice, but it isn’t universal. There’s the case of foldr1 that I mentioned above. The foldr1 function’s name doesn’t tell you anything about the result, really; it tells you what operation is being performed, how it is being performed and what you should expect to give it as arguments. Again, I don’t see any way around this. And, I guess we could see the “fold” portion of foldr1 as a noun – (what do you get? You get the fold), but that feel like a rationalization.
The thing is, programs in Haskell appear to be fundamentally
different from programs in traditional OO languages. Haskell seems to pull you into a mode where
you are using language mechanisms: lists, pattern matching, and higher-order
functions to express your solution, and when you do, you get to use powerful
functions that operate on those abstractions. The only mechanism built into an OO language is method dispatch, and the
bias is toward making up your own home-grown abstractions with noun-verb syntax. The foldr1
function is all about lists; it has nothing to do with your problem at all. Once could argue that it should really be an
operator, something resigned to punctuation-character status, but that just
sidesteps the issue. In Haskell, the
fundamental parts of your program are lists, data types, and functions that
operate on them. It appears that you can
move Haskell programs closer to a DSL-like style, and in fact, Paul Hudak does
that in a music example in School of
Expression, but the code I’ve seen so far isn’t very DSL-ish.
I’ve gotten this feeling that perhaps cryptic naming is an integral part of the Haskell experience. Maybe, given Haskell's semantics, there just aren’t enough verbs to go around. What do I mean by that? Let’s consider foldr1 one more time. If we were trying to do something similar in OO we’d probably place the function on the List class, wouldn’t we? List could have a foldRight function and a foldLeft function. We could distinguish folds with starting values via overloading. There’s no real need to encode a distinction in the method name; no need for a “1”.
No, I don’t know whether it is the structure of the language or the culture, but there’s something which has pushed the community toward abbreviations and encoded names. I wonder if it will ever look normal to me. And, if it does, will I be delighted or scared?
Regarding the shorter names: I think the reasoning is that when considering whether to optimize for the first time you encounter a new function (which happens once), or the thousands of subsequent times that you type it out later, it is better to have a short name that pays off thousands of times rather than a long and descriptive name that pays off once when you've never seen it before (and has other drawbacks like making lines wrap more frequently and thus making the code harder to read).
Also, if you were new to functional programming, there is no name, no matter how long, that would make the function intuitive, since you wouldn't have encountered folds before. After you learn what a fold is, and that left and right describe two different orderings of operations, then foldl and foldr are very intuitive. Anybody who knows what a fold is will immediately know what they refer to. As to foldl1, you have to look it up, the first time. That doesn't seem like such a big deal. In Java, even with longer names and phenomenal IDEs, you still have to look at javadocs, and sometimes dig in to library code to figure out how something works or what it does.
Posted by: forcing names is lame | January 25, 2007 at 03:30 AM
Nope, I completely agree with you, Michael. The names are unnecesarily cryptic, and a short name doesn't save as much time as a descriptive name gives. To quote an Ada advocate: saving keystrokes is the job of the text editor, not the language. The names aren't even the same as in other functional languages, and they follow no consistent pattern. Show doesn't show anything, for instance, and scans don't scan anything - they are also called unfolds in scheme, which is slightly better on the naming business.
But haskell is just to cool to give up over such a small issue. As long as it doesn't turn out to be a symptom of a general careless attiude to readability, but I don't think it does.
Posted by: Harald Korneliussen | January 25, 2007 at 04:40 AM
You wrote: "No, I don’t know whether it is the structure of the language or the culture, but there’s something which has pushed the community toward abbreviations and encoded names."
Well, short names are usually a sign of a "core" concept--something that you're expected to take for granted. For example, C programmers write "if (x) y else z;" instead of the more descriptive "if_the_value (x) is_true_then_do y else_do z." The second tells a better story, but it gets old fast. Or consider the C expression "a || b", which would read something like "evaluate (a) and_return_the_result_if_true otherwise_evaluate (b) and_return_that". Again, the cryptic abbreviation is a bit nicer, because it's a core concept.
Now, I'm going to claim something strange: "foldr" is as fundamental in Haskell as "if" statements are in C.
Sound strange? Go look at the source code for Prelude.List:
http://www.haskell.org/onlinereport/standard-prelude.html
Almost every function that manipulates lists is based on foldr or foldl!
So if you want to grok Haskell, you should try to figure out what's so fundamental about "fold", and how it relates to recursion. (See "Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire" for a detailed discussion. And consider the fact that both halves of Google's "MapReduce" architecture are folds!)
At this point, the observant Haskell newbie might say, "Well, I guess that could explain foldr. But half the Haskell libraries out there are also filled with short, cryptic names! Look at 'em: monads, arrows, etc., etc. Can all these things really be fundamental?"
And, terrifyingly enough, the answer is yes: The fundamental operations on monads are "map", "unit", and "join," and each of these is a fundamental as "if" or "||" in C. The fundamentals of arrows include such operators as "&&&" and "|||", and again, these are essential basics.
So that's life in Haskell: First, you'll need to learn a new set of fundamentals. And then, on a regular basis, some library author will try to pry your brain open and dump in even _more_ "fundamental" ideas.
This implies two things: (1) Haskell will teach you a staggering number of fascinating things, and (2) advanced Haskell code is basically impenetrable until you've mastered the underlying mathematical concepts it uses.
It can be a mixed blessing at times, I admit. :-)
Posted by: emk | January 25, 2007 at 08:12 AM
You're thinking too procedurally ;-).
It's tail and not getTail because
> tail the_list
*is* the tail of the list, it doesn't get the tail of the list.
> tail [1,2,3]
and
> [2,3]
are exactly equivalent.
All functions in Haskell are get-functions: getFoldr, getMap, getProduct...so why would you want to put get in front of them?
Posted by: Jules | January 25, 2007 at 08:31 AM
Re: short names: IMHO, it's not so much the CompSci as the Mathematics influences that led to the use of short names in FP in general, and in Haskell in particular.
Re: folds: Graham Hutton wrote a good paper that helps show the importance of folds in FP: "A tutorial on the universality and expressiveness of fold"
http://www.cs.nott.ac.uk/~gmh/bib.html#fold
Posted by: scruzia | January 25, 2007 at 08:38 AM
The terminology of fold is not particularly unique to Haskell. In other languages, it is sometimes referred to as "Accumulate", "Reduce" or "Collect". Or you can use the actual technical term of Catamorphism.
Posted by: Chris Rathman | January 25, 2007 at 09:36 AM
Michael Feathers wrote "If I say account.deposit(2) in an OO program, or account.getBalance() ... tail [1, 2, 3]. In this case, the function is given a noun name; it’s named after its result."
In a /Java/ program we might say
account.getBalance()
In a Smalltalk program we would say
account balance
Do you find it strange that Java Vector has methods named
firstElement()
lastElement()
Here's a snippet from an old Smalltalk style guide
- Use imperative verbs and phrases for methods which perform an action
- Use common nouns for methods which answer a specific object
Posted by: Isaac Gouy | January 25, 2007 at 11:58 AM
Fold *is* a verb. You fold your data structures up like a piece of paper. You start using a lot of folds/unfolds and you get origami programming. As for "head" and "tail" they look more like adjectives to me. Don't think of it as a verb drought, think of it as adding some of the other parts of speech.
Posted by: Greg Buchholz | January 25, 2007 at 04:18 PM
I also think that part of it is the expression-form of the language itself.
Haskell expressions tend to bulk up width wise more than imperative languages. Perhaps the short names are partly due to attempting to keep these lengths manageable. Yes, I know that you can split lines, but sometimes it is unideal.
Posted by: mgsloan | January 25, 2007 at 07:02 PM
One is surprised that the "linguistic" affectations of APL didn't come up in the course of the conversation. Yet. Not sure whether I invoke it as a "could be worse" and "when I was a kid" kindof thing, or as a partial explanation of Haskell's parsimonious sensibility. I do honestly wonder if there's a relation in the latter case; surely the people behind Haskell learned functional programming in APL (like my wife and I did) way back when in the dark ages....
Posted by: Bill Tozier | February 14, 2007 at 03:59 AM