[Haskell-cafe] Re: learning advanced haskell
jwlato at gmail.com
Mon Jun 14 12:04:43 EDT 2010
> From: Aran Donohue <aran.donohue at gmail.com>
> Hi Cafe,
> I've been doing Haskell for a few months, and I've written some mid-sized
> programs and many small ones. I've read lots of documentation and many
> papers, but I'm having difficulty making the jump into some of the advanced
> concepts I've read about.
> How do people build intuitions for things like RankNTypes and arrows? (Is
> the answer "Get a PhD in type theory?") Normally I learn by coding up little
> exercise programs, but for these I don't have good intuitions about the
> kinds of toy problems I ought to try to solve that would lead me to
> understand these tools better.
I read as much as I can. On the cafe, Planet Haskell, papers,
whatever. If I don't understand a paper, I leave it be for a while
then come back to it in a few weeks/months. Usually I'll be able to
make further headway.
Also don't try to read too much at once. The literature is quite
dense and I find it useful to let things sit and percolate for a
For the specific cases of RankNTypes and arrows I had nearly opposite
experiences. I understood RankNTypes immediately, in that I expected
higher-rank behavior and was surprised when code didn't work that way.
Arrows were particularly difficult for me (for much the same reason
as the Monoid class, incidentally). I understood the concept of
arrows, but was unable to figure out how to actually *do* anything
with them for some time. I'm still not sure that I have a great idea
of when arrows are useful as a computational abstraction; they seem to
be used more for the notation and convenience functions than anything
> I understand the motivation for other concepts like iteratees, zippers and
> functional reactive programming, but there seem to be few entry-level
> resources. John Lato's recent Iteratee article is a notable exception*.
This is exactly what I was aiming for, so I really appreciate hearing
your comments. I'm a beginner in many areas as well, so I also would
like to see more entry-level resources.
> Hints? Tips? "Here's how I did it"? _______ is a great program to write to
> get to learn ______?
Stuff I have a decent understanding of:
RankNTypes - Look at this function
> makeString :: (Show b, Show c) => (a -> a) -> b -> c -> String
> makeString f b c = show (f b) ++ show (f c)
If you try to call it as e.g.
> makeString id 0 "foo"
GHC provides a type error. This doesn't work because when GHC unifies
the types, it does something like this:
1) the first argument, f, is a function with type a -> a
2) the function f is applied to an Integer, so f :: Integer -> Integer
3) the function f is applied to a String, so f :: String -> String
4) there are conflicting types for f, so bail out
Even though you declare f :: a -> a, that 'a' must be set to exactly
*one* value within the call, where makeString tries to apply it to
values with different types. This is rank-1 polymorphism. RankNTypes
fixes this by allowing us to specify that the "f" function is supposed
to be polymorphic within the makeString function (rank-2 polymorphism)
as opposed to the standard rank-1 polymorphism:
> makeString2 :: (Show b, Show c) => (forall a. a -> a) -> b -> c -> String
calling makeString2 does the right thing. That's pretty much the
whole story of Rank2Polymorphism. RankNPolymorphism is exactly the
same with nested levels of forall's.
One really useful feature of this is that it provides a sort of
inversion-of-control for polymorphism. Typically the caller has the
ability to define the types when a function is called, as happens with
the b and c parameters to makeString2. But in the first argument the
*callee* determines the type values of the function! The caller knows
only that the function must take one argument and return a result of
the same type (which also means "id" is the only interesting function
of the appropriate type). The callee is free to instantiate the given
function with *any types it wants*. This is also what makes the ST
monad safe incidentally.
Arrows - Toy project: write a small program and use arrow notation to
combine functions. Write the zipWith version of the "fib" function
with arrows. Most of the stuff on the web is pretty readable IMHO.
Yampa is a good place to move beyond "Arrow" and into "ArrowLoop".
Zippers - Toy project: anything that keeps track of decisions in a
tree. Mapping a path as in Theseus and the Zipper seems good. I
learned it by implementing a user history function.
Template Haskell - Toy project: programatically generating data
declarations seems like a good starting point because it's relatively
simple and can't be done with normal code. Instances are a good
follow-up. Of course this is only a small portion of what TH can do,
but it's a start. Bulat's tutorials are the best on this topic I'm
I left out a lot of stuff that I'm still working with myself (SYB,
Finally Tagless, category-extras, ...).
> Thanks in advance,
> * Even in this article, he busted out (>=>). And it appears that the
> iteratee library's actual design is more sophisticated than the design
Well, yes. If the library implementation (or Oleg's examples) were
the same as the implementation I presented, there probably would have
been no need for the article!
More information about the Haskell-Cafe