[Haskell-cafe] What is your favourite Haskell "aha" moment?

Neil Mayhew neil_mayhew at users.sourceforge.net
Wed Jul 11 16:11:30 UTC 2018


I came to Haskell from C++, and I was used to the idea of parametric 
types and functions from C++ templates.

However, what I really liked about Haskell's way of doing these was 
that, due to type inference, not only is there a lot less ceremony 
involved, but code is generic (parametric) *by default*, and thus much 
more reusable, although you still have the option of tightening it up by 
adding a type annotation (eg for performance reasons). For a while, I 
was writing all my C++ code as templates, but this ended up being a pain.

Also, traditionally C++ has not allowed you to place any constraints on 
template arguments (type parameters) whereas Haskell's type classes are 
a very elegant way of doing it. (C++ now has concepts, but I haven't 
taken the time yet to see how they compare with type classes.)

I was also used to function overloading, which is somewhat similar to 
type inference, in that the compiler will pick an implementation based 
on the types of a function's arguments. This is similar to Haskell 
picking an implementation from among type class instances. However, what 
blew me away is that Haskell can overload based on *return type* as well 
as argument types. I haven't seen any other production-ready language 
that can do this. A great example of how this is useful is the regex 
library, where you can select from among widely varying styles of regex 
matching result simply by type inference, ie without needing any type 
annotation.

There were a *lot* of other things I found amazing, but others have 
covered many of these already. Also, languages are borrowing from each 
other at a rapid rate these days (eg Rust traits are equivalent to type 
classes) so it's hard to find a "killer feature" in Haskell any more 
(except laziness, perhaps). I think it's more the well-balanced 
combination of all the features that makes Haskell so pleasant to work 
in, and it's hard to demonstrate all of these in a single example.

My own favourite "gem" is this code for computing all primes, based on 
code in a paper[1] by Doug McIlroy:

primes = sieve [2..] where sieve (p : ns) = p : sieve [n | n <- ns, n 
`mod` p /= 0]

I think care must be exercised, when using examples like this one, to 
avoid giving the impression that Haskell is a "toy" language. However, 
what I find interesting about this example is that all other sieve 
implementations I've seen work on a fixed size of sieve up front, and if 
you later change your mind about how many primes you want, eg because 
you're expanding a hash table and want a bigger prime for the size, you 
typically have to start the sieve from scratch again.

[1]: http://www.cs.dartmouth.edu/~doug/sieve/sieve.pdf
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/haskell-cafe/attachments/20180711/fb9b02cb/attachment.html>


More information about the Haskell-Cafe mailing list