News for hackers. Stuff that matters.
Re:Corecursion by Pitarou on Thursday May 08, 2014 @05:58PM (#7717979)
This is the missing piece from my mental toolbox. I wish I'd known about it years ago.

Co-recursion is a principled way to turn a top-down algorithm into a bottom-up one. For instance, in Haskell, it's easy to write the fibonacci series from its definition:

    fibonacci 0 = 1
    fibonacci 1 = 1
    fibonacci n = fibonacci (n - 1) + fibonacci (n - 2)
But this is ridiculously inefficient. Until now, the only "principled" way I knew of to transform this into something sensible was through memoization, but that's still very wasteful.

With co-recursion in my toolkit, I can transform this into a form that generates the whole series as a stream:

    fibonacci_series = 0: 1: more fibonacci_series
      where
        more f1: rest@(f2: _) = f1 + f2: more(rest)
And thanks to Haskell's lazy, garbage collected semantics, taking the nth element of the series (remembering to drop data I don't need) turns out to be equivalent to:

    fibonacci n = fib n 0 1
      where
        fib 0 a b = a
        fib n a b = fib (n-1) b (a+b)
Which is what we wanted. :-)
Re:Corecursion by jgg on Thursday May 08, 2014 @09:00PM (#7718720)
Corecursion is useful for taking a recursive algorithm and transforming it to a stream-like output pattern.

But this is ridiculously inefficient.

Well...you might be surprised in the general case. Because of lazy evaluation, Haskell won't necessarily implode on non-TCO recursive functions (example: http://stackoverflow.com/questions/13042353/does-haskell-hav...), and will actually sometimes cause a stack overflow on "optimized" functions.

Until now, the only "principled" way I knew of to transform this into something sensible was through memoization, but that's still very wasteful.

I think "real" Haskell code usually favors operations of lists over recursive functions. That said, the standard way to transform your recursive structure into something "sensible" is to use a tail-recursive function. In basically any other functional language, you'd go with that approach.

To get the same "benefit" in Haskell, you'd have to force strict evaluation inside of a tail-recursive function. This prevents a thunk from causing problems. That said, Haskell doesn't always build up a normal stack on a regular recursive call.

Otherwise, you'd just use a list structure.

(Someone correct me if I've said something stupid.)

ref:

http://www.haskell.org/haskellwiki/Tail_recursion

Re:Corecursion by Pitarou on Thursday May 08, 2014 @11:24PM (#7719152)
My remarks were addressed to people who don't need tail recursion or the benefits & drawbacks of lazy evaluation explained to them, so I think they went a little over your head.

I presented a recursive definition, a stream based definition, and a tail-call definition of the fibonacci function. In that toy example, it's easy to get between the three different forms, but in many cases the connection is far less obvious. We need principles that unite the different forms, and allow us to move between them. Co-recursion is one of those principles.

Re:Corecursion by jgg on Friday May 09, 2014 @01:46AM (#7719558)
so I think they went a little over your head.

I understand lazy evaluation and tail recursion fine. I interpreted your comment as presenting corecursion as the only logical alternative to naive recursive algorithms with or without memoization.

You've tacked on the part where you say the latter algorithm is equivalent (due to Haskell's evaluation) - I get that. I'm still not understanding what you mean by only knowing inefficienct, naive recursion in contrast to corecursion. In practice, I have rarely seen corecursion or naive recursion used, but maybe we read different code.

In that toy example, it's easy to get between the three different forms, but in many cases the connection is far less obvious. We need principles that unite the different forms, and allow us to move between them. Co-recursion is one of those principles.

Uh, okay.

Re:Corecursion by Pitarou on Friday May 09, 2014 @02:32AM (#7719649)
I once read a textbook introducing the theory of algorithms that started with the Fibonacci series. The author gave a formal definition, and translated this directly into a naive, recursive algorithm. Then he showed how to improve the algorithm with memoization. This was a real revelation to me: using this one simple, generally applicable transformation, all kinds of algorithms can be improved.

Then the author introduced the iterative Fibonacci algorithm, and formally proved that the iterative and recursive algorithms were equivalent. But the author failed to explain where that iterative algorithm came from: there was no general purpose tool like memoization that could be used to derive the iterative form from the recursive form. I still remember the intellectual disappointment.

Now, with co-recursion and a couple of other techniques, that gap is finally bridged. Using co-recursion, I can derive the stream algorithm directly from the recursive definition. And then I can apply deforestation techniques and tail-call optimization to derive the iterative algorithm from the stream algorithm. That's a pretty powerful intellectual toolset, don't you think?

Re:Corecursion by jgg on Friday May 09, 2014 @11:21AM (#7721334)
I once read a textbook

I think I've read that book too.

For the benefit of anyone following along at home: The unproven-but-accepted Church-Turing thesis states that any algorithm that can be done on a Turing machine (i.e., a Turing-complete language) can be represented as a recursive function and a function in the lambda calculus, and vice-versa.

And then I can apply deforestation techniques and tail-call optimization to derive the iterative algorithm from the stream algorithm.

How do you use deforestation and tail-call optimization to derive an iterative function from a stream in the general case?

You're also pulling a false comparison: you've jumped from a 'general' algorithm to "what a corecursive function is evaluated as under Haskell's semantics".

Corecursion builds a data structure. A TCO function, for example, won't produce that kind of output. The corecursive function could only be directly equivalent to the linear time, constant memory TCO function in a lazily-evaluated runtime (if that's true - tail recursive functions in Haskell can actually blow up the stack due to lazy evaluation).

Re:Corecursion by Pitarou on Friday May 09, 2014 @08:30PM (#7724002)
How do you apply deforestation ... in the general case?

How do you cut an iron bar in half with a pair of scissors?

Deforestation is a tool; not a universal truth.

You're also pulling a false comparison ... The corecursive function could only be directly equivalent to the linear time, constant memory TCO function in a lazily-evaluated runtime

But I'm using a lazily evaluated runtime. My code is Haskell. And in any case, I used deforestation in the last step to remove the lazily evaluated data structure.

If I understand you correctly, you're telling me that high-level mathematical formalisms work better in Haskell. Yes, they do.

Re:Corecursion by gamegoblin on Thursday May 08, 2014 @06:31PM (#7718119)
Alternatively, making use of laziness and recursive definitions:

  fibonacci n = fibs !! n
  fibs = 1:1:zipWith (+) fibs (tail fibs)
Re:Corecursion by Pitarou on Thursday May 08, 2014 @07:41PM (#7718418)
Your version is certainly pretty, and it's based on an insight into the nature of the fibonacci series

But the value of co-recursion is that you don't need that insight. When you expand out the definitions of `tail`, `!!` and `zipWith`, your code turns out to be the same as mine[1], so I consider that a big win for co-recursion.

[1]: At least I think it does.

Re:Corecursion by shoki on Friday May 09, 2014 @12:07AM (#7719300)
Not corecursive, but here's the SICP O(ln(n)) algorithm:

    fibonacci = fib 1 0 0 1
      where
        fib a b p q n
          | n == 0 = b
          | even n = fib a b p' q' (n `div` 2)
          | otherwise = fib a' b' p q (n - 1)
            where
              p' = p*p + q*q
              q' = 2*p*q + q*q
              a' = a*q + b*q + a*p
              b' = b*p + a*q
Re:Corecursion by Osmium on Thursday May 08, 2014 @07:19PM (#7718331)
> But this is ridiculously inefficient.

I'm sorry, but I don't understand this. How is that inefficient?

Re:Corecursion by gamegoblin on Thursday May 08, 2014 @07:52PM (#7718468)
Here is the call stack for 1, 3, and 5

    fib 1
      1

    fib 3
      fib 2
        fib 1
          1
        fib 0
          1
      2
      fib 1
        1
      3

    fib 5
      fib 4
        fib 3
          fib 2
            fib 1
              1
            fib 0
              1
            2
          fib 1
            1
          3
        fib 2
          fib 1
            1
          fib 0
            1
          2
        5
      fib 3
        fib 2
          fib 1
            1
          fib 0
            1
          2
        fib 1
          1
        3
      8
The number of calls grows exponentially. Roughly 1.618^n, to be specific.
Re:Corecursion by dustingetz on Friday May 09, 2014 @12:38AM (#7719414)
Haskell is lazy and pure, why can't it auto memoize for us so we can code it like we would in math?
Re:Corecursion by albertzeyer on Friday May 09, 2014 @03:35AM (#7719795)
A while ago, I asked exactly that question:

http://stackoverflow.com/questions/5749039/automatic-memoizi...

I think the answer is basically that there is no good heuristic about what to store, how much and how long.

Re:Corecursion by cabalamat on Friday May 09, 2014 @09:01AM (#7720575)
> I think the answer is basically that there is no good heuristic about what to store, how much and how long.

In Python you can just put a @memoize decorator before a function you want memoized; would it be possible to have something similar in Haskell?

Re:Corecursion by albertzeyer on Friday May 09, 2014 @09:08AM (#7720605)
Yes, see here: http://www.haskell.org/haskellwiki/Memoization

Note that in Python, you also have the issue that you still need to specify some cleverness about how long something is stored and what is stored. Many example memoize implementations just store everything (e.g.: https://wiki.python.org/moin/PythonDecoratorLibrary#Memoize) but of course that is not a solution in practice.

Re:Corecursion by gizmo686 on Friday May 09, 2014 @11:22AM (#7721337)
I don't think the methods mentioned in the haskell wiki are quite equivelent to the python decorator, as they require you to modify the data structure itself to support memoization.

It should be possible to use UnsafePerformIO to create a generic function memoize function (at least for inputs that implelemt Eq or Ord), but that is almost definantly overkill and asking for space leaks.

Re:Corecursion by nodejsguy on Thursday May 08, 2014 @11:32PM (#7719177)
If this was reddit, you would get gold for that reply
Re:Corecursion by gamegoblin on Friday May 09, 2014 @12:11AM (#7719311)
In the time it took me to do it by hand I am 100% positive I could have written a Python program to do it probably 3 times.

Yyyyep just tested it:

    def fibs(n, tabs=0):
        if n < 2:
            print "\t"*tabs + "1"
            return 1
        print "\t"*tabs+"fibs %d"%n
        a = fibs(n-1, tabs+1)
        b = fibs(n-2, tabs+1)
        print "\t"*tabs+str(a+b)
        return a+b
Took exactly 71 seconds to write. I probably spent 3 minutes on it by hand getting all the spaces right.
Re:Corecursion by Aqwis on Thursday May 08, 2014 @07:33PM (#7718376)
It makes two recursive calls at each step.
Re:Corecursion by alepper on Thursday May 08, 2014 @07:33PM (#7718380)
Try sketching the call graph of e.g. fibonacci 5.
Re:Corecursion by wyager on Thursday May 08, 2014 @07:35PM (#7718390)
Alternatively,

     fibonacci n = (map fst $ iterate (\(a,b) -> (b,a+b)) (0,1)) !! n
Re:Corecursion by Pitarou on Thursday May 08, 2014 @08:01PM (#7718513)
I think gamegoblin's was prettier.
Re:Corecursion by wyager on Sunday May 11, 2014 @07:21PM (#7730451)
Got a really short one!

     fib a b = a : fib b (a+b)
     fibn = (!!) (fib 0 1)
Re:Corecursion by LBarret on Friday May 09, 2014 @12:49PM (#7721845)
did you read SICP (this is explained in the first chapters of the book) ? if not you're for a treat.