
Why learning Haskell makes you a better programmer - dons
http://dubhrosa.blogspot.co.uk/2012/12/lessons-learning-haskell.html?m=1
======
charlieflowers
This is a great article.

To me, the most powerful example is this: "It forced me to think of every such
problem as a chain of the primitive list operations, maps, folds, filters and
scans. Now I always think in these terms. I "see" the transformation of a
container as a simple sequence of these operations. "

That's a higher level of thinking that is faster and yet at the same time more
precise than thinking in terms of imperative loops.

Haskell has taught him to think more powerfully.

~~~
beagle3
I recommend people study APL / J / K for the same reason: It has the right
primitives that make everything expressible as maps, folds, filters, scans,
ranges, and stuff like that.

Even more so than Haskell.

It doesn't have monads and the kind of abstractions that lets you modify
control flow semantics. It doesn't even have the facilities to build abstract
data types - which makes you work with less-abstract data, and realize that
although some abstraction is useful, most of what is practiced today is
useless.

APL / J / K promote, at the same time, container abstraction, and concrete
down-to-the-metal real work.

~~~
tel
I've always wanted to dive into these guys. Is there a good introduction for
fluent Haskell programmers somewhere?

~~~
Someone
I haven't done anything in Haskell, but I doubt that makes much of a
difference.

Maybe more inspiration than introduction, but the "Game of Life in APL" video
is a must see
([http://uk.youtube.com/watch?v=a9xAKttWgP4&fmt=18](http://uk.youtube.com/watch?v=a9xAKttWgP4&fmt=18))

The description on YouTube points to a "Game of Life" tutorial at
<http://tryapl.org>. I haven't tried it, but it looks nice.

For a more academic (as in "read about, don't play with") introduction, I
found Iverson's "Notation as a Tool of Thought"
(<http://awards.acm.org/images/awards/140/articles/9147499.pdf>) a good
introduction.

------
laureny
> Haskell makes your functions pure by default. You have to change the type
> signature if you want to do IO

The author seems to be conflating IO and side effects. IO is just one of the
many ways that side effects can occur.

> Purity also means you can't read or write global variables, that's just
> another type of IO.

Er... what? No, not at all.

Reading global variables is not side effects, much less IO.

Writing global variables is side effects, but has nothing to do with IO.

> Functions that do IO are difficult or impossible to test.

Absolutely not, IO functions are not harder to test than regular ones. Is your
function creating a file? Start your test, assert the file doesn't exist, call
your function, assert your file exists. Done.

> I now put all my IO in top-level functions that are called directly from
> main

This is great for homework, but in the real life, IO needs to be performed
throughout your entire application, and unsafePerformIO is just not an option.
You have to use iteratees for this kind of operation, which adds a pretty
thick layer of complexity to your code base.

> boo :: Map Integer String -> String -> Integer

This syntax is clean but it doesn't tell me anything that

def boo(map: Map[Integer, String], key: String) : Integer

doesn't tell me.

> The point is, you can't confidently reason about a function from its
> signature if IO is involved.

Sure, you can: the mention of IO tells me this function operates within the IO
monad, so I know _a lot_ of operations and laws that apply to it.

Haskell is a fantastic language but this article is doing a very poor job at
explaining why. I suspect its author has only recently started using Haskell
and would have been better advised to wait a bit before sharing his
enthusiasm.

~~~
tome
I'm not sure if it's due to a misunderstanding, but many of your comments seem
to be incorrect or nonsequiturs.

> > Haskell makes your functions pure by default. You have to change the type
> signature if you want to do IO

> The author seems to be conflating IO and side effects. IO is just one of the
> many ways that side effects can occur.

Indeed, but that does not refute the OP's statement.

> > Functions that do IO are difficult or impossible to test.

> Is your function creating a file? Start your test, assert the file doesn't
> exist, call your function, assert your file exists. Done.

Right, and (in my opinion) that is more difficult than testing something that
doesn't create a file.

> > boo :: Map Integer String -> String -> Integer

> This syntax is clean but it doesn't tell me anything that

> def boo(map: Map[Integer, String], key: String) : Integer

> doesn't tell me.

Indeed, but again that does not refute the OP's statement which was simply
that the syntax is clean.

> > The point is, you can't confidently reason about a function from its
> signature if IO is involved.

> Sure, you can: the mention of IO tells me this function operates within the
> IO monad, so I know a lot of operations and laws that apply to it.

You do? Please tell me a law that applies to IO.

~~~
Martijn
> > boo :: Map Integer String -> String -> Integer

> This syntax is clean but it doesn't tell me anything that

> def boo(map: Map[Integer, String], key: String) : Integer

> doesn't tell me.

OP's point was not so much that the Haskell syntax is clean, but more that
there is a huge difference between

Map Integer String -> String -> Integer

and

Map Integer String -> String -> IO Integer

The latter is allowed to do IO, while the former is not. There are very few
languages that allow you to express this difference and have the compiler
enforce it (Haskell being one of them). So "Map Integer String -> String ->
Integer" tells you a lot more than "def boo(map: Map[Integer, String], key:
String) : Integer" does.

For more information about what exactly types tell you, look up parametricity
or read Philip Wadler's "Theorems for free".

~~~
sampo
_"Haskell being one of them"_

Also Fortran being one of them (you can mark functions and subroutines
'pure').

------
Posibyte
I've felt the power of Haskell before, through tidbits I've learned and books
like Learn You A Haskell. However, Haskell still presents itself as a giant
rock in my mind, somewhat impenetrable and obtuse. It's so different from my
normal thinking processes that my brain has an incredibly difficult time
adjusting. It's like my thinking process turns to goo after the first few
layout steps.

Basically, I wish I could understand Haskell so I could harness its power.

~~~
tmhedberg
For what it's worth, I would suggest just writing code as a means of building
your intuition. Don't worry about understanding every last detail; just hack
away at it and intuition will come as a natural consequence of your
persistence. At least that has been my experience. (Honestly, this is probably
true of most things that are challenging to learn, not just unusual
programming languages.)

I felt much the same way as you when I first decided to dig into Haskell and
went through a period of some frustration in which I felt that, although I was
more or less understanding the literal text of the books and documentation I
was reading, I was lacking a feel for the "big picture" of how it all fit
together. I could explain specific details of how the language worked but I
couldn't really visualize how to put together a complete, useful program in
it.

For whatever reason, something about Haskell really intrigued me despite my
frustration, and I kept at it. There was never any major epiphany where
everything just clicked, though there were a few minor moments of
enlightenment, as when I discovered that the neat pattern I had started using
in some of my code actually had a name and a well known type class:
"Applicative". I also recall reading a particular article about monads (and
manually working through its examples) that made me suddenly realize how
utterly simple they were, despite the ridiculous hype.

After writing enough Haskell code, it eventually dawned on me that I "got it".
I wasn't struggling anymore. Haskell had, without my noticing it, become my
default, natural way of thinking and reasoning about code, and it just made
sense in a deeply fundamental way. I could no longer even see why I'd been
confused in the first place.

Professionally, I've used mostly Java and Ruby, and have never written a
single line of Haskell for pay. But like the author of this article, I've
found that it shapes the way I write code in other languages, even those far
removed from pure FP, in significant ways. My mental design process when
writing new code at work usually takes place in terms of Haskell's data types
and abstractions, and then gets translated into whatever language I'm actually
using once I feel like I have a good handle on the direction that I'm heading.
This usually results in nice, clean designs, consisting mostly of pure,
composable functions and immutable data with as little shared state as I can
get away with, arranged, when necessary, around an imperative, IO-driven core.
Basically the same way I'd structure actual Haskell code.

If you're intrigued enough to keep at it, then patience is all you need, and
you'll get there. It's pretty fun when you do.

------
wzdd
I'm sure the author is right about IO, but these sorts of articles always
include such trivial examples that they do the opposite of the intended job.
The one code snippet is from a dictionary look-up function. Here it is in
Haskell:

    
    
        boo :: Map Integer String -> String -> Integer
    

And here it is in C++ as a standalone function:

    
    
        template <class Key, class Value> Key boo (const Map<Key, Value> &, const Value &);
    

or, more idiomatically, as part of a class:

    
    
        template <class Key, class Value> class Map {
           […]
           Key boo(Value &val);
        };
    

The author's point is that the Haskell code can't do much other than use its
parameters, because there is no IO involved, whereas the C++ version could do
anything. Well, sure, it _could_ , but let's be reasonable (which is all we
can be without reading the code): it's probably gonna map a string to an int.
It's just as likely that the Haskell code could do weird stuff (how does it
behave if it finds multiple candidates for Value?) as the C++ code,
practically speaking.

So this argument actually turns out to be an argument for good containers, or,
more generally, for good built-in types. The author could make all those
assumptions about his function from the type signature because he/she knew
what "Map" meant, not because of anything inherent in Haskell's type system. I
mean, I grant that Haskell has a great type system, and great built-in types.
It's just that this example doesn't make the argument it claims to make.

Let's look at some code I'm writing now. Here is strstr:

    
    
        boo :: String -> String -> Int
    

Not too helpful really. Here is a function which searches
ComplexDataStructure, which contains file and directory names as well as other
data, for partial matches of "string", returning a "goodness" value indicating
the closeness of the match for the highest-matching candidate:

    
    
        boo :: ComplexDataStructure -> String -> Int
    

I challenge anyone to work out what the function does from that. Sure, you can
make more assumptions about the Haskell version, but, practically speaking, IO
cleanliness has little to do with it. What matters here is knowing what the
data structures do, and using decent types.

~~~
reycharles
I think your C++ example corresponds more to the following haskell type.

    
    
        boo :: Map a b -> b -> a
    

since it nowhere mentions int.

~~~
mifrai
I think the Haskell example should've used this type signature instead.
Compared to the OP's example, this has a much stronger implication. There's
very few implementations of this type that would make sense, whereas with the
other one - it could be 'count' of the number of times 'string' shows up in
the map; Or it could be number of letters in the provided string (ignoring the
map entirely); Or it could be (key - 1); or any number of other things.

Here, you can't just magic into existence or modify an unknown 'a' value.

~~~
kscaldef
I think it's still pretty unclear what this function might do, since the
inverse of a map isn't well-defined. I think this is a type signature that
makes sense and has a fairly obvious most-likely meaning:

    
    
        boo :: Map a b -> b -> Set a
    

I'm not sure how you would conjure up just an 'a' in general.

~~~
Peaker
You also need an Ord or at least an Eq constraint on the b's.

------
venomsnake
Rant: I needed an hour to get pointers and an afternoon to grasp what function
pointer is in C. 10 minutes to grok recursion, and OOP (some parts of it) came
as a just a sane way to do stuff ... I am hitting my head in the monad brick
wall close to a month now.

Aside from that it is an amazing language that really kicks you to think
differently.

~~~
necubi
Copied from a comment of mine from a couple of years ago:

"I've recently finished reading Learn You A Haskell For Great Good!, and I
think the author's approach to monads works well. I'd read various
metaphorical explanations that only served to further cloud the issue, but
thanks to LYAH I finally got it.

In short, he has you using monads long before you understand them (Maybe and
IO in particular), then slowly introduces monads by first explaining functors
and applicative functors.

In retrospect, the mystique seems crazy. Monads are just not that confusing:
they're simply values with added context, along with functions that let you
interact with those values without losing the context. It's a shame that this
powerful idea is so obscured by its supposed difficulty."

\---

I'm not going to give you any more metaphors (a monad is not a burrito).
Monads are objects (in the category theory sense) that in some sense "contain"
another object, and let you work on both the level of the contained object and
the box around it. This can be terribly useful (and necessary in a pure
language like Haskell).

It turns out that monads obey the so-called functor laws (which is a just
another scary category theory word for a really simple idea) and thus have the
function `map` defined on them. What's really cool is that we can map
(transform) the boxed data inside the monad without caring whether there's
actually anything inside.

Why is this useful? Consider the Maybe monad in Haskell (or Option in Scala).
Lets say you want to get data out of a HashMap. You look up a specific key,
but there might not be any data there. In most languages, this would be
handled by returning a null or throwing an exception. Both solutions break
type safety, so we want something better. Using Maybe we don't have to care
whether or not the data is present to operate on it:

    
    
      // This is Scala because my Haskell is a bit rusty, but the
      // ideas are the same
      val map = new HashMap[String, String]("hello" -> "goodbye")
    
      // HashMap$get returns, in this case, an Option[String]
      val opt1 = map.get("hello") // => Some("hello")
      val opt2 = map.get("bonjour") // => None
    
      // What happens when we process these two options?
      opt1.map((x) => x.toUpperCase) // => Some("HELLO")
      opt2.map((x) => x.toUpperCase) // > None
    
      // Note that `map` here is the ">>=" operator in Haskell
      // It's different than a normal map, which takes in something
      // of type A, a function from (A -> B) and produces something
      // of type B. ">>=" instead takes in something of type M[A],
      // a function from (A -> B) and produces something of type
      // M[B]. The powerful thing about this construction is that
      // it lets us concatenate processing steps:
    
      opt1.map(_.toUpperCase).map(_ + "!!!").map("!!!" + _)
         //=> Some("!!!HELLO!!!")
      opt2.map(_.toUpperCase).map(_ + "!!!").map("!!!" + _)
         //=> Nothing
    
      // If we start out with Nothing, we always have Nothing, but
      // we don't need to check for it at every step.
    
    

No conditional code checking for nulls, just type-safe processing on possible
unreliable inputs.

And Maybe/Option is possibly the simplest monad. You're probably more familiar
calling map on lists, and it turns out list is also a monad, with the empty
list representing the Nothing value. Things get trickier when we move into the
work-horse monads in Haskell: IO and State, but the same principles apply. The
IO monad lets us process the results of running IO operations without having
to care (until we need to) about whether the options have succeeded or not. It
also, crucially, allows us to run IO operations in a determined order by
chaining the operations together. The details of all this are too long for a
HN comment, so I'll just direct you to Learn You a Haskell [0] if you haven't
read it, or Real World Haskell [1] if you have. Both have pretty good chapters
on monadic thinking which hopefully will get you over the hump.

My best advice is: don't be scared by the category theory. Contrary to popular
opinion, you don't need a PhD to program in Haskell :).

Good luck!

[0] <http://learnyouahaskell.com/chapters> [1]
<http://book.realworldhaskell.org/read/>

~~~
tomjakubowski
Nitpicking, but:

    
    
      // of type B. ">>=" instead takes in something of type M[A],
      // a function from (A -> B) and produces something of type
      // M[B].
    

The type of ">>=" is M a -> (a -> M b) -> M b, which lets you 'chain' the
result of one monadic function (a -> M b) to another monadic function. So, for
example:

    
    
      return :: String -> IO String
      putStrLn :: String -> IO ()
      getLine :: IO String
      return "foo" >>= putStrLn :: IO () -- prints "foo"
      getLine >>= putStrLn :: IO () -- gets a line of input, then prints it

~~~
necubi
Oops, yeah, the map function above composes its argument with return, giving
it the type M a -> (a -> b) -> M b, making it equivalent to the Haskell
`liftm` function.

------
mitchi
I'm also fairly interested in Haskell but I think that studying data
structures and algorithms extensively will make me a better programmer. Not
learning a whole other way to program... That will not work. I am already very
comfortable in C++/Java/D and other imperative languages. I can do the same
work faster because I can use global state to get easier solutions to
problems. If I want pure functions, I'll write a pure function. If I want
shorter, more readable code I'll write a macro. Or maybe not.

~~~
ky3
I'm genuinely curious how you'd prepare for a multicore and cloud computing
future.

Using global state is like blowing Gabriel's horn -- everything dies!

Ok, so not everything, but the upshot is that global state plays very badly in
the future of computing.

~~~
mitchi
I feel very comfortable actually, since I'll program in the same language that
the OS uses. I'll feel completely at home. The Haskell language uses the
operating system to switch threads and send datagrams. And isn't the OS the
biggest user of global state? It's like doing Yoga in a Karate class.

------
alayne
I recommend some other functional language like Scheme or OCaml. All I got
from my time with Haskell was frustration. I read RWH and worked through
tutorials. I never got to a point where it didn't seem like the simplest tasks
weren't painful.

~~~
tomjakubowski
Real World Haskell isn't a very good introductory text. Some of the early
chapters are riddled with errors and confusing, contradictory instructions.

Learn You a Haskell For Great Good! is one of the better introductory
programming books I've read, and the author writes in a ridiculously fun and
easy-to-read style.

<http://learnyouahaskell.com/>

~~~
ericssmith
"ridiculously fun and easy-to-read style"

But if you find 'fun' and large amounts of text distracting, then Graham
Hutton's 'Programming in Haskell'[1] is a better introduction to what Haskell
is about.

[1] <http://www.cs.nott.ac.uk/~gmh/book.html>

------
millstone
_You have to change the type signature if you want to do IO... Purity also
means you can't read or write global variables, that's just another type of
IO._

If you've written so much Haskell that you think of global variables (!) as a
form of IO, then perhaps you would benefit from writing in C for a while?
Beware internalizing abstractions so completely that you confuse them with the
reality.

~~~
fmap
This came up a few times already and it really isn't as wrong as it sounds. In
Haskell all "side effects", which include memory stores, are in the IO monad
(with a few exceptions).

Writing a global variable is a side effect, because you change something other
than the return value of a function. Reading would still break functional
extensionality: Calling a function with equal arguments might yield different
results in different contexts.

------
zvrba
Haskell helps you develop your imagination:

> I have no idea what the word "boo" is supposed to mean when used as a
> function name. But I can be almost certain what this "boo" does. It takes a
> map that has Integer keys and String values, and a second argument that is
> just a String, and it returns an Integer. So I'm fairly sure that this
> function does a reverse mapping

~~~
jeremyjh
Actually its just deductive reasoning. Because that example is a pure
function, you know there is little else it could be doing. The only question
in fact would be whether or not it is doing a transformation on the value
before returning it.

~~~
lobster_johnson
Things it could be doing:

* Counts the values that have the second argument as a substring

* Returns key that, using some fuzzy string algorithm, matches the second argument the closest

* Computes a score from matching the second argument against the map, which tracks document occurrences in a search engine type gizmo

I don't think the meaning is obvious at all. If anything it's a lesson in
naming stuff: Don't call your functions "boo".

~~~
tel
The actual function would be written more like

    
    
        invMap :: forall k v. (Eq v, Eq k) =>
                  Map k v -> v -> Set k
    

The parametricity means that while this function may get specialized to have v
~ String and k ~ Int, we can be sure that invMap isn't using that information.
Instead, with the typeclass restriction, we know that the only thing the
function can be doing internally to the ks and vs is comparing them for
equality.

Intuitively, invMap can either be computing the Set of ks which hold a
particular v or, maybe, the set of ks which _don't_ hold that value and not
much else.

------
dchichkov
As long as people don't abuse that power and don't write C++ code, full of
statefull functors (intellegent fools they are, the worst kind), I'm happy.

~~~
beagle3
This.

A lot of Haskell's power comes from it being able to enforce the validity of
this style of programming. C++ does not - and when working with C++, you have
to reason at both the C++ level like you always do, and at the Haskell level
(but without the compiler verifying your work).

That's extra burden without extra benefits.

~~~
dchichkov
It is just that author in the "Container Operations" says that using functors
in C++ is such a good practice.

No. While concept is nice, in practice, with C++ it can result in time waste
and unreadable/unmodifiable code with no clear benefits. Cleaning up some
statefull functors overdesigned by some intellegent fool can be a gruesome
job.

~~~
laureny
> Cleaning up some statefull functors

This makes no sense at all. A functor is a type class, it doesn't contain any
state.

~~~
wonderzombie
In Haskell, it makes no sense, agreed.

In C++, it makes sense: <http://www.sgi.com/tech/stl/functors.html>. Functors
are function objects, which is to say they're a class/struct with one method,
operator().

If this makes you sigh, know that you're not alone.

------
decasteve
Analogous to this I found category theory gave me a better/different view
understanding of modern algebra and set theory.

~~~
tel
My god yes! Reading through a book or two on category theory is astoundingly
useful for improving your deductive chops. I'm not interested in declaring it
a new foundation for mathematics, but it's such a useful language!

------
dschiptsov
Sure. Instead of learning the power of composition of high-order functions,
you will learn that particular composition, with a trendy name, and will start
to argue on forums, that this particular function composition is somehow
superior to all other compositions and you must do everything using that
particular composition, which is called monad..)

There is one question, before you press "down-vote" few times - what is the
sequence of machine instructions your monad compiles into?

~~~
tmhedberg
Who is arguing that the monad as a means of composition is superior to all
others? Or that Haskell doesn't emphasize the power of composition of high-
order functions? Function composition is one of the most important tools in a
Haskell programmer's toolbox. Monads are just another one of those.

The thing is, most popular languages these days support higher-order
functions. It's nothing to write home about. Monads get a lot of attention
specifically because they are a fairly unique idea that is not supported out-
of-the-box in other languages. People who are advocating (or evangelizing, if
that's your perspective) for a particular thing are going to focus primarily
on the specific aspects of that thing that make it stand out from the crowd.
For Haskell, that means things like monads, algebraic data types, non-strict
evaluation, functional purity, etc.

 _There is one question, before you press "down-vote" few times - what is the
sequence of machine instructions your monad compiles into?_

I can't understand what you're getting at here. What difference does it make
what code the compiler emits, unless I am trying to aggressively optimize low-
level performance? If it does what I intend it to do, and is reasonably fast
at doing it, I don't care about the specific instructions. How is that
relevant?

~~~
dschiptsov
The question was why should one have to learn some sophisticated, over-
engineered type system along with unreadable notation, when the result will be
plain, sequential machine code anyway? What is the point besides "I'm so
clever"?

~~~
Peaker
Your claim that it is over-engineered is unsubstantiated, and from a Haskell
user's point of view, plainly false.

The claim that the notation is unreadable is similar.

Haskell's notation is certainly unfamiliar, and that can be frustrating (as
all unfamiliar notations are), but it is one of the best designed ones out
there.

As for the question: Why learn the type system? Because it helps you encode
your program's invariants as types -- allowing them to be verified _cheaply_
and _repeatedly_ by an automated prover (the type checker), rather than being
documented and checked by _expensive_ tests.

They are also documentation you can rely on, as it is guaranteed to stay in
sync with the program. It means you can get up to speed with other's code much
quicker.

They also restrict code in _useful_ ways, encouraging code that is more
concurrency-friendly, simpler, and generally better.

~~~
dschiptsov
OK, those are arguments in favor of strong-typing, early-binding and compile-
time checks. Java arguments, in other words.

I think, looking at sources of arc.arc or news.arc, that it is, how to say,
over-hyped.

~~~
Peaker
Strong typing is not nearly as good when the type system is weaker.

Java's "strong typing" doesn't protect you from null dereference errors, as
one example out of many.

Also, strong typing is not as good when you have to specify a _lot_ of the
types repeatedly. Without good type inference, it loses a lot of the appeal.

Strong typing is not nearly as good when every function has arbitrary effects
not specified by its type.

Strong typing is not as good when it is harder to do parametericity, as
discouraging it means you get monomorphic types, which are far less effective
at preventing errors.

Haskell gives a whole lot more benefits via static types than Java does.

------
simgidacav
OT: why did you post the url with the "m=1" parameter in the end?

------
dudehuh
I swear if I see another gray on white web page, I will throw my own S#!7 at
you like a 900 pound gorilla in a zoo!

------
diminish
And learning Lisp will make you a better Haskell programmer. Read PG's arc
tutorial.

~~~
Peaker
Indeed, extending your repertoire is definitely a good thing.

------
pretoriusB
I'd also like to see an article about how learning Haskell makes you prone to
preaching to other people about how better programmer it made you and why you
should learn it too, despite not having much in terms of actual production to
show for it...

~~~
charliesome
You totally missed the point.

~~~
pretoriusB
I don't care about the point. I've dabbled in Haskell myself. I just hate the
preaching, especially when it's unsubstantiated.

~~~
mbetter
He gave concrete examples for all of his points, unlike you.

~~~
pretoriusB
Wait, what?

I spoke against a concrete and specific thing, this very article, what more
"examples" do you want? Pointing to a particular thing is the very definition
of an example.

So, yes, this kind of article is very often seen, and we're very familiar with
the genre to need more examples. It even admits so itself, as it starts with:
"It's OFTEN CLAIMED that learning Haskell will make you a better programmer in
other languages.".

As for the articles' examples: meh. Using Haskell signatures to sketch public
APIs in other languages is somehow better that using any other similar
notation? Even if it was so, he never proves it better. Just some handwaving.
And his other big point is about thinking in terms of maps, sets, filters,
reduces and such. How is that unique to Haskell? Lisps beg to differ...

~~~
magnet_ball
I think he fails to mention the very strong static typing in addition to
method signatures. There are no nulls, everything is expected ahead of time,
you know what you are going to get.

