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

Could you suggest a simpler example?

Finding primes is something that is taught in the first programming class in Indian high schools. I guess I've never thought of it as something hard.

I looked at nodejs.org, and their first example is a web server! Python has the Fibonacci as it's second example (the first one show's numeric operations).

Ruby does simple string operations on it's home page. While I think that is indeed simpler, it's a little nuanced in Haskell. Depending on how you're doing it you'll need to Map toUpper from Data.Char or use toUpper from Data.Text, and I don't think it's a good first impression have something that uses Data.Text, and the OverloadedStrings extension.

PS - Although, I do agree with some other commentors here that this code isn't actually the Sieve of Eratosthenes, and is far more inefficient, and that is a valid reason to replace that example.



Fibonacci sounds perfect!

  fibonacci :: Integer -> Integer
  fibonacci 0 = 0
  fibonacci 1 = 1
  fibonacci n = fibonacci (n - 1) + fibonacci (n - 2)
Arguments for this:

* "Find the Nth Fibonacci Number" is among the most universally known programming tasks, so visitors are far more likely to immediately pick up the example than they are with sieve.

* It shows off a bit of Haskell syntax that (A) can be learned just by looking at an example like this, (B) has a clear benefit to readability that any programmer can appreciate, and (C) is a syntax not found in most mainstream languages.

* The visitor needs no functional programming experience to follow it; it doesn't even use any higher-order functions! This is important, as many visitors will be completely new to FP, and an example that they can't follow is not going to be effective at encouraging them to continue reading.


That's a horrible algorithm though, it takes exponential time. Any good implementation of Fibonacci numbers would be logarithmic in the number of arithmetic operations and polynomial in overall running time (because the numbers get bigger). Here's a good implementation in Haskell: http://nayuki.eigenstate.org/res/fast-fibonacci-algorithms/f... , and the same in Python: http://nayuki.eigenstate.org/res/fast-fibonacci-algorithms/f... . BTW, I think even functional programmers would find the Python code slightly easier to follow :-)


Remember, the target audience is newcomers to Haskell. The purpose of a code sample is to give them a glimpse into the syntax of the language, which should be understandable enough that it piques their interest to learn more.

It would be to the detriment of haskell.org to showcase a superior algorithm that is harder for newcomers to follow.


Maybe it's because I've been using Haskell exclusively for a few months, but I find the Haskell example more clear. This surprises me because I have much more experience with Python.


I think the factorial function is even nicer: everyone with basic high school math has seen it. It's even shorter.

Also, I think the example should not be a partial function ;).


Your example doesnt highlight lazyness like the sieve one does. I would expect to see something like this instead:

    fibonacci :: [Integer]
    fibonacci = 1 : 1 : zipWith (+) fibonacci (tail fibonacci)


True, but remember that, say, a Rubyist arriving at haskell.org has no knowledge of:

* what zipWith does

* that (+) is a function being passed, not an operator being invoked

* cons syntax for 1 : 1 : ...

* how this could terminate (having no preexisting knowledge of laziness, remember)

A better goal than "show off all the features of Haskell" is "show a Haskell example that's understandable, demonstrates a clear benefit, and makes you want to learn more."

If a newcomer encounters so much unfamiliar territory that Haskell comes across as too alien to be useful, that only reinforces existing negative stereotypes about it.

Better to give a first impression of "you absolutely can pick up Haskell, and it'll be nice!"


> True, but remember that, say, a Rubyist arriving at haskell.org has no knowledge of:

> * what zipWith does

haskell:

  zipWith f xs ys
ruby:

  xs.lazy.zip(ys).map(&f)
While Ruby doesn't have a method of the same name, its not exactly foreign.

> * that (+) is a function being passed, not an operator being invoked

Again, sure, Haskell has different syntax than ruby, but the parens signal something special is going on here.

> * cons syntax for 1 : 1 : ...

Sure, but if you know what the Fibonacci sequence is -- and the reason it and primes are almost without exception the two things chosen for these infinite stream examples in every language is because its presumed that programmers do, its pretty easy to get the idea of what is being done from knowing what the function is trying to do and looking at the values presented.

> * how this could terminate (having no preexisting knowledge of laziness, remember)

A Rubyist that has no knowledge of laziness isn't a very knowledgable Rubyist, since laziness is an important and central concept in Ruby's Enumerable module (one of the core modules most frequently used by Rubyists.)


> Could you suggest a simpler example?

Anything that can be Googled and understood what it is trying to accomplish is less than 2 minutes.

I tried Googling "sieve" and got nothing of use, then "prime sieve" that has a good wikipedia article that is (probably?) about the correct thing but doesn't fit into the "easily understood in a couple of minutes) rule.

So... literally anything. Hello world. First impression shouldn't be that you need to be a math expert to use the language.


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...




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

Search: