Custom Search

Wednesday, April 27, 2011

Why Haskell?

This should not be considered an expert overview of the language. It's a deep language, and I'm still learning it. This is a discussion of what I've seen so far to explain why I chose it.

As I mentioned, I'm not learning Haskell because I expect it to be a marketable skill; I'm learning Haskell because I expect it to improve my programming skills in general. That should happen because it's radically different from other languages I know, so it's idioms should be new to me, and will thus provide more options when working in other languages. At least one person regrets this - because they wind up thinking about how much simpler things would be in Haskell.

As a final bonus, there appears to be more higher mathematics in use when writing Haskell than in most other languages. So I'm hoping to get a little more use from my mathematics degree than I've gotten so far.

Things I look for in a language

Being well-designed

I've heard it claimed that Haskell is in the LISP family. Looking at a typical program, you'd have to wonder about that - it looks more like Perl, with obscure operators scattered throughout the text. Once I figured out what's really going on, I could see the logic of the claim. It's at least as valid as the claim that Scheme is a descendant of Algol. Outside of the type system (more on that later), there's a bit more syntax than LISP, but not a lot. Most of a programs code consists of function calls and binding the value of those calls to names - just like functional code in LISP. The most prominent difference is that, where LISP has one way to express a function call - (function arg arg arg) - Haskell has two: function arg arg arg (without LISPs parenthesis) and arg `function` arg for functions of two arguments. The tricky part is that a valid symbol quoted as shown is an operator, and an operator enclosed in parenthesis is a function name. So 2 + 3 = (+) 2 3 = 5.  Basically, it's a syntax tweak that lets the programmer decide if something is better used as a function or an operator both at definition time - by choosing a name that's an operator or a symbol - and at use time, by choosing to change the function name from one to the other.

So it's not as chaotic as it seems. And the bit about math plays into it - the design of the various functions and the function application system all seem to have a bit of mathematical logic in them.

High-level data types

Haskell has a nice selection of high-level data types. Unlike most languages I've dealt with - Clojure being the most notable exception - they're all immutable. Since evaluation is lazy (another thing that makes it different from other languages), lists - or streams, if you prefer - feature prominently. Another thing making it a LISP language. But it also has tuples, arrays, and hash-maps - though the latter aren't quite up to the performance levels I'm used to. Something about writing immutable hash maps with both a fast lookup and insert (which creates a new map) being difficult.

Boilerplate minimization

This is part of the static vs. dynamic type checking debate. One argument goes that static type checking catches errors at compile time rather than test time, so shortens development time. The counter-argument is that typing in type information takes time and adds more places for errors, so lengthens development time.

Haskell lands squarely on both sides. The compiler does static type checking at compile time. However, it also infers the types of most things, so you almost never have to provide type information. From what I can tell, type information is provided more often specifically to nail down a complex functions type so the compiler will complain if you get it wrong than because the compiler can't infer a type.

Further, Haskell one ups most dynamic type checking languages when it comes to reducing boilerplate. If not having to provide type information for parameters cuts down development time by removing places you can make mistakes, then not having to provide names for the parameters should be even more effective. There's a Haskell style that encourages doing just that. Instead of defining a function with parameters that returns  the result of the calculations with those parameters, you define a function without parameters that returns a function that does the calculations when passed those parameters.

Support for concurrent programming

Seems to be there as well, in a set of features similar to Clojure. Both languages use immutable data structures and functional techniques to make the bulk of a program concurrency-safe by default. Clojure has some container types that will raise exceptions if used in ways that aren't concurrency-safe. Haskell has a type system that captures the notion of not concurrency-safe, so you get compile-time errors if you try such things accidentally.

The REPL

Yup, the Haskell implementations I've looked at all have a REPL.

Plays well with others

At least one implementation can compile to native code on a number of systems as well. There seem to be wrappers for most of my favorite C/C++ libraries, so the external function support should be quite good.

A note about homoiconicity

Haskell isn't homoiconic. That means it can't have real (i.e. LISP) macros. Which means LISP is more powerful. Right?

Maybe. LISP macros are generally used for one of three things:
  1. Controlling evaluation to create new constructs. That's built into Haskell.
  2. Creating domain specific languages. The ability to make operators functions and vice versa pretty much covers this.
  3. Automated code creation. Um...
Ok, Haskell doesn't have that one. And that is one of the more powerful uses of macros. There is an extension to Haskell (Template Haskell) that covers this. It's not portable. It's poorly documented. It's works an ASTs instead of source code. But it is there.

I'll be blogging about my experiments with Haskell as well, and possibly revisiting some of the things I did in Clojure. Stay tuned...