Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

This article is flawed at its core: monads do not, in general, compromise type inference and they are not all about sequencing state.

Sure, they are used for state and IO, but they can do far more, like nondeterministic programming, continuations or even nothing at all. Ultimately, I would say monads are about composition: they let you define exactly how to compose computations.

Additionally, monads do not break type inference. Having a type for every top-label binding is considered good style in Haskell, but it is still optional. I could see this practice being confused for type inference not working. There are of course language extensions that do break type inference in certain cases, and sometimes it is impossible even with the standard language, but it works almost all of the time even with monads.

Also, and this is probably just because this article is for years old, it really overstates hire bad Haskell performance is. In my experience, even naively written code runs relatively fast--certainly fast enough for me--in most cases. I believe this has really improved in recent times. Haskell is certainly better in this regard than most other high-level languages.



This article definitely shows its age. These are the kinds of criticisms the Haskell community has been working quite obsessively to counter for some time. Thus the emphasis on explaining monads in a friendly matter and impressive optimization work that's been put into place.

---

And yeah, monads are much more about a particular type of composition or sequencing. Compare the common composition types (warning, scary but harmless jargon to follow)

  class Monoid m where
    e    :: m
    (<>) :: m -> m -> m

  class Functor f => Applicative f where
    pure :: a -> f a
    ap   :: f (a -> b) -> f a -> f b

  class Functor f => Monoidal f where -- isomorphic to Applicative
    unit :: f ()
    prod :: (f a, f b) -> f (a, b)

  class Applicative m => Monad m where
    return :: a       -> m a
    join   :: m (m a) -> m a

  class Applicative m => Monad' m where -- isomorphic to Monad
    return :: a       -> m a
    bind   :: m a -> (a -> m b) -> m b

  class Profunctor f => Arrow f where
    arr   :: (a -> b) -> f a b
    (>>>) :: f a b -> f b c -> f a c

    -- kind of ignore this one :)
    first :: f a b -> f (a, c) (b, c)
You can see a progression in the constraints on how things combine as you move down the list. First you have plain composition of non-container Functor types.

Then you have the Applicative/Monoidal functors (they're equivalent/isomorphic) which are probably most clearly demonstrated by the Monoidal class--it implements composition of Functors which maps to products in the contained types (or, has a "applicative" product which commutes with the functor)!

Then you have Monadic functor composition where you have "sequential" or "inward" composition via join (compare to Applicative's "horizontal" product composition) which can be implemented by the 'bind' function which maps a function that rewraps contained values.

Finally you have the Arrow types which add composition to Profunctors (which are like functors which contain both "incoming" and "outgoing" values) by composing them like a category.

---

Which is a lot of words to say that (1) Monads are just one of a whole group of "kinds" of composition and (2) they basically represent composition which allows for control over how functorial contexts get sequenced.


It's probably true that simply written Haskell code is fast enough for most uses. The trouble comes when that code isn't fast enough, or should be faster.

How do you make Haskell code faster? Apparently it's somewhat like writing fast Java code: avoid most features, use primitive data types, and write like you're writing C. Haskell code optimized for performance ends up like messy C code more often than not, except that unsafePerformIO isn't such a prickly topic in C.

Even Haskell professionals often don't know when "free" GHC optimizations will kick in, because the optimizations can be so picky, even after you remember to apply some forgotten language pragma. Writing performant Haskell is a black art.


Only one of the Haskell shootout programs uses `unsafePerformIO` now (n-body). While it looks to me like most of them make fairly heavy use of Foreign and thus low-level types, I don't think this automatically makes Haskell code like messy C code. After all, there still isn't pointer arithmetic, types don't magically transform into other types behind the scenes, and there's still a fair degree of non-strictness. More importantly (and contrary to C's "messiness") it's seldom difficult to take Haskell optimized with these low-level features and wrap it up in a nice high-level package for use from elsewhere. It's a curious and wonderful property of GHC that you can enable all the extensions you want on a module-by-module basis without creating a lot of cross-module drama. These are properties other languages should strive for.

Writing exceptional, highly performant Haskell is still a black art, sure. But writing performant Haskell is basically writing Haskell with a modicum of knowledge and respect (less lazy IO, more ByteString, more iteratees, etc). Writing highly performant Haskell is becoming easier and easier; the runtime system will give you detailed statistics about where your problems may lie, tools like ThreadScope can give you all the information you'd ever want, and many of the commonly-used libraries are heavily optimized for speed (Text, ByteString, iteratees, etc) which you benefit from basically for free.

It's not a panacea but I haven't ever had to use anything more sophisticated than strictness annotations, and even that I use pretty sparingly. Like the best black arts, it's very seldom necessary, and becoming more seldom every day.

As a side note, I would love to see an alternative "honest" shootout where programs must not do much overt "doping" (such as using Foreign and `unsafePerformIO`) because it would be interesting to compare the "native" speeds of different languages, with the kind of code the best normal people would write for themselves or their employer. Haskell has always abused its low-level facilities for higher placement in the shootout than it really deserves.




Consider applying for YC's Summer 2026 batch! Applications are open till May 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: