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

Well, for one, it's a bad sieve algorithm.

I think a neat algorithm to demonstrate laziness and Haskell clarity would be enumerating the Calkin-Wilf rationals. [0] It's quite a bit longer but demonstrates a number of neat ideas. I'll start first with a derivation which demonstrates all of the structure of the algorithm and then go through a series of mechanical transforms so that by the end I have a one-liner and a comparable Python implementation.

The first algorithm comes directly from the paper and uses an intermediary infinite tree to represent the rationals.

    data BTree a = Node a (BTree a) (BTree a)

    fold :: (a -> x -> x -> x) -> BTree a -> x
    fold f (Node a l r) = f a (fold f l) (fold f r)

    unfold :: (x -> (a, x, x)) -> x -> BTree a
    unfold f x = let (a, l, r) = f x in Node a (unfold f l) (unfold f r)

    breadthFirst :: BTree a -> [a]
    breadthFirst = concat . fold glue where
      glue a ls rs = [a] : zipWith (++) ls rs

    allRationals :: Fractional a => [a]
    allRationals = breadthFirst (unfold step (1, 1)) where
      step (m, n) = ( m/n, (m, m+n)
                         , (n+m, n) )
In 16 lines I've got an infinite binary tree, its natural fold and unfold, a breadth first search, and a lazy algorithm for generating all of the rationals with no repeats. The whole thing is simple, natural, beautiful, and efficient! It demonstrates infinite recursive types, laziness, higher-order functions, and bounded polymorphism.

And also a neat algorithm!

The downside is that 16 lines is pretty long.

By inlining the fold and unfold I can get it down to 9 lines:

    data BTree a = Node a (BTree a) (BTree a)

    breadthFirst :: BTree a -> [a]
    breadthFirst = concat . glue where
      glue (Node a ls rs) = [a] : zipWith (++) (glue ls) (glue rs)

    rats :: Fractional a => [a]
    rats = breadthFirst (generate (1, 1)) where
      generate (m, n) = Node (m/n) (generate (m, m+n)) (generate (n+m, n))
If I'm allowed imports we can use Data.Tree and make this a one-liner!

    import Data.Tree

    allRationals :: Fractional a => [a]
    allRationals = flatten (unfoldTree step (1, 1)) where
      step (m, n) = ( m/n, [ (m, m+n), (n+m, n) ] )
Finally, if I go another route and fuse the fold and unfold together into a hylomorphism

    data Trip a x = Trip a x x deriving Functor

    hylo :: Functor f => (f b -> b) -> (a -> f a) -> a -> b
    hylo phi psi = phi . fmap (hylo phi psi) . psi

    allRationals :: Fractional a => [a]
    allRationals = concat (hylo glue step (1, 1)) where
      glue (Trip a ls rs) = [a] : zipWith (++) ls rs
      step (m, n) = Trip (m/n) (m, m+n) (n+m, n)
we can hide the tree entirely and demonstrate `deriving`... at considerable cost to clarity! With a little more golfing (read: inlining) we arrive at this beauty:

    allRationals :: Fractional a => [a]
    allRationals = concat (go (1, 1)) where
      go               = glue . next . step
      next (a, b, c)   = (a, f b, f c)
      glue (a, ls, rs) = [a] : zipWith (++) ls rs
      step (m, n)      = ( m/n, (m, m+n), (n+m, n) )
which at least has the bonus of demonstrating some nice co-recursion between go and next. Or even, ultimately:

    allRationals :: Fractional a => [a]
    allRationals = concat (go 1 1) where go m n = [m/n] : zipWith (++) (go m (m+n)) (go (n+m) n)
which is actually kind of nice again if almost all of the structure has vanished.

Note that if `interleave` were part of the Prelude then we could write

    allRationals :: Fractional a => [a]
    allRationals = go 1 1 where go m n = (m/n) : interleave (go m (m+n)) (go (n+m) n)
given

    interleave :: [a] -> [a] -> [a]
    interleave []     ys     = ys
    interleave xs     []     = xs
    interleave (x:xs) (y:ys) = x : y : interleave xs ys
which is a little prettier and directly comparable to something Pythonic like

    from fractions import Fraction
    from itertools import islice

    def interleave(x, y):
      while True:
        yield x.next()
        yield y.next()

    def all_rationals():
      def go(m, n):
        yield (m/n)
        for v in interleave(go(m, m+n), go(m+n, n)):
          yield v
      return go(Fraction(1,1), Fraction(1,1))

    def rationals(n):
      return list(islice(all_rationals(), n))
[0] http://www.cs.ox.ac.uk/jeremy.gibbons/publications/rationals...


It's also utterly incomprehensible for someone who hasn't seen Haskell before. Whereas with the existing example, one can at least piece together an idea of what's going on.

The point is to demonstrate the directness of expression and conciseness of Haskell, not to show how to create an efficient implementation of an involved algorithm.


I agree that the first formulation is a bit incomprehensible, though two-liner breadth-first search is understandable if a bit amazing.

Some of the latter versions (and perhaps ultimately the very last version) are easier to walk through for a beginner, though, and are calculated from properties expressed in the first.


To be honest: This would turn me off even more than the current example which is also hard to read/understand as a non Haskell programmer. The fibonacci example in another comment in this thread however is very easy to understand and would fit much better.


Even the one-liner form at the end?

This comment was really bad at exposition, but I think it got somewhere nice.


Well the one-liner form I probably have overseen. It's a lot better than the other variations but I do think that the fibonacci example does show Haskell in a much more understandable way than the allRationals one-liner.

But maybe that's a bit me: I don't particularly like one-liners because as an outsider it takes usually a bit more time to understand it than more lines..


Wonderful comment, but I have a math degree and Haskell experience. This stuff would be insane to drop on a beginner.


I'm hoping to simplify it!


I haven't digested it completely yet, but I suspect that the definition of `next` in `allRationals` is incorrect

     next (a, b, c)   = (a, f b, f c)
I can't find the declaration for `f` (probably go?)


It is, sorry. I was writing that all without checking it and I can't edit now.

I wrote a post elaborating the ideas here: http://tel.github.io/2014/07/09/calkin_wilf_for_early-ish_ha...




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

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

Search: