tags:

views:

1053

answers:

10

Here are the best implementations I could find for lazy infinite sequences of Fibonacci numbers in both Clojure and Python:

Clojure:

(def fib-seq (lazy-cat [0 1]
 (map + fib-seq (rest fib-seq))))

sample usage:

 (take 5 fib-seq)

Python:

def fib():
 a = b = 1
 while True:
  yield a
  a,b = b,a+b

sample usage:

for i in fib():
  if i > 100:
    break
  else:
    print i

Obviously the Python code is much more intuitive.

My question is: Is there a better (more intuitive and simple) implementation in Clojure ?

Edit

I'm opening a follow up question at Clojure Prime Numbers

+10  A: 

Obviously the Python code is much more intuitive.

It's not. It's "more imperative", and for a lot of people imperative tends to be more obvious because that's what they're used to, but that is a matter of perception, and is strictly subjective.

Pavel Minaev
Actually using generators (yield) is an example of functional programming.
Roskoto
No, they're more like an example of coroutines. http://en.wikipedia.org/wiki/Coroutine
Chris Lutz
That's not really what "intuition" is about. If you know what is Fibonacci sequence, but no programming experience, you can see that the Python version is adding some numbers in a loop. In the Clojure version, you have no idea what's going on unless you know what `rest` and `map` do.
Lukáš Lalinský
Obviously the corountines are very useful in some cases and as far as I know there is no such in Clojure. Is there a useful substitute?
Roskoto
Thank you Lukas, that is exactly my point.
Roskoto
@Lukáš Lalinský - Not necessarily. The Clojure version more closely mirrors the mathematical definition: `fibs(x) = { 0 if x = 0; 1 if x = 1; fibs(x-2) + fibs(x-1) if x >= 2; }` (Of course, a Haskell version can almost perfectly mirror this, but this entire argument is apples to oranges.) So the Clojure version may be a bit clearer for a mathematician.
Chris Lutz
Well, I personally don't see how the Clojure code maps to the mathematical definition. I'd need to look up the functions in the documentation to understand it. All I can understand from it is "+" (I have no problem writing functional programs in Haskell). Obviously the recursive version would be clearest to a mathematician, but that's not the issue here.
Lukáš Lalinský
Chris, is this the exact Haskel code or a pseudo-code? Now that is intuitive ! :)
Roskoto
@Roskoto - That's mathematical notation (wiggled a bit to fit in code blocks designed for programmers). The Haskell code would be `fib 0 = 0; fib 1 = 1; fib n = fib (n - 1) + fib (n - 2)` (replace `;` with line breaks), which is pretty much equivalent.
Chris Lutz
@Chris Lutz except that Haskell code calculates the Nth number, not an infinite sequence of numbers :)
Lukáš Lalinský
@Chris So I hope this is not just a recursion which is horribly inefficient. How exactly this works in Haskell?
Roskoto
@Lukáš Lalinský - We can do that: `fibs = map fib [0..]` now `fibs` is an infinite sequence of Fibonacci numbers. Of course, a more commonly seen and possibly better way to get that sequence is closer to the Clojure code. Maybe I should start posting my own answer...
Chris Lutz
@Roskoto - It is recursion, and it is probably inefficient. The common definition of an infinite list of Fibonacci numbers in Haskell is going to be essentially the same as what you see in Clojure.
Chris Lutz
@Chris "Maybe I should start posting my own answer..." Thanks I'll appreciate that :) For the Haskell thing I must agree with Lukas it is not an equivalent code because it computes each number separately (and inefficiently)
Roskoto
A: 

Think about how would you write lazy-cat with recur in clojure.

Tadeusz A. Kadłubowski
+12  A: 

I agree with Pavel, what is intuitive is subjective. Because I'm (slowly) starting to grok Haskell, I can tell what the Clojure code does, even though I've never written a line of Clojure in my life. So I would consider the Clojure line fairly intuitive, because I've seen it before and I'm adapting to a more functional mindset.

Let's consider the mathematical definition, shall we?

       { 0                   if x = 0 }
F(x) = { 1                   if x = 1 }
       { F(x - 1) + F(x - 2) if x > 1 }

This is less than ideal, formatting wise - those three brackets lined up should be one giant bracket - but who's counting? This is a pretty clear definition of the Fibonacci sequence to most people with a mathematical background. Let's look at the same thing in Haskell, because I know it better than Clojure:

fib 0 = 0
fib 1 = 1
fib n = fibs (n - 1) + fibs (n - 2)

This is a function, fib, that returns the nth Fibonacci number. Not exactly what we had in Python or Clojure, so let's fix that:

fibs = map fib [0..]

This makes fibs an infinite list of Fibonacci numbers. fibs !! 1 is 1, fibs !! 2 is 1, fibs !! 10 is 55, and so on. However, this is probably quite inefficient, even in a language that relies on heavily optimized recursion such as Haskell. Let's look at the Clojure definition in Haskell:

fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

The first couple of characters are pretty simple: 0 : 1 : makes a list with elements 0 and 1, and then some more. But what's all the rest of that? Well, fibs is the list we've already got, and tail fibs calls the tail function on our list so far, which returns the list starting at the 2nd element (sort of like in Python saying fibs[1:]). So we take these two lists - fibs and tail fibs - and we zip them together with the + function (operator) - that is, we add the matching elements of each. Let's look:

fibs       = 0 : 1 : ...
tail fibs  = 1 : ...
zip result = 1 : ...

So our next element is 1! But then we add that back onto our fibs list, and look what we get:

fibs       = 0 : 1 : 1 : ...
tail fibs  = 1 : 1 : ...
zip result = 1 : 2 : ...

What we have here is a recursive list definition. As we add more elements to the end of fibs with our zipWith (+) fibs (tail fibs) bit, more elements become avaliable for us to work with when adding elements. Note that Haskell by default is lazy, so just making an infinite list like that won't crash anything (just don't try to print it).

So while this is perhaps theoretically the same as our mathematical definition before, it saves the results in our fibs list (sort of an auto-memoization) and we rarely have the problems that might be experienced in a naive solution. For completeness, let's define our fib function in terms of our new fibs list:

fib n = fibs !! n

If I didn't lose you yet, that's good, because that means you understand the Clojure code. Look:

(def fib-seq (lazy-cat [0 1]
 (map + fib-seq (rest fib-seq))))

We make a list, fib-seq. It starts with two elements, [0 1], just like our Haskell example. We do a lazy concatenation of these two initial elements with (map + fib-seq (rest fib-seq)) - assuming rest does the same thing that tail does in Haskell, we're just combining our list with itself at a lower offset, and then combining these two lists with the + operator/function.

After working this through your head a few times, and exploring some other examples, this method of generating factorials becomes at least semi-intuitive. It's at least intuitive enough for me to spot it in a language I don't know.

Chris Lutz
Chris, first thanks for the nice explanation it is useful in it's own way to someone trying to understand the Clojure code.But actually you've proved my point. The Python code doesn't need the huge explanation :)
Roskoto
The Python code has _had_ an explanation: about 50 years of imperative programming style has explained it to you. I wouldn't know much about either language's code if I didn't know programming. Granted, the Haskell/Clojure programs may hurt my brain a little more as I try to learn the languages, but really you consider them more readable/intuitive because you're used to imperative and procedural languages, like most programmers are. Learning a new mindset or paradigm is going to take a lot of explanation. How long did it take you to learn generators? Or pointers (if you know C)?
Chris Lutz
As I understand it the yield command denies the idea of call stack in general. I'm pretty sure it is not something common from the last 50 years (except the scheme's call/cc). I still insist that the sample Python code is far from imperative.
Roskoto
The yield concept isn't tremendously common, but it has been discussed by Knuth and at least implemented in C (though not shared) by Tom Duff by the time he invented his device in 1983 (http://www.lysator.liu.se/c/duffs-device.html - see the second to last paragraph), and generators specifically originated in 1975 from a language called CLU, which was procedural and object-oriented. And neither of the Wikipedia pages for "procedural programming" or "imperative programming" contain the word "stack." The call stack is not a fundamental concept for either paradigm, just an implementation detail.
Chris Lutz
`yield` is imperative. It doesn't have anything to do with the stack - if you issue commands ("statements" - `yield` in Python is a statement), then it's imperative.
Pavel Minaev
Thank you, Pavel, for the argument I should be making. Though at this point, I'd like to note that, since we've been arguing this long on what is and isn't intuitive to us, and since we disagree on what we found intuitive, you (Roskoto) are by definition wrong in your blanket assumption that X is more intuitive than Y for all programmers.
Chris Lutz
Is it true that everything that could be implemented with yield has its Clojure equivalent? I thought that if Clojure had this "feature" too it will be even better but looks like I'm the only one having this opinion.
Roskoto
In the worst case scenario, you could implement generators in Clojure yourself. Some people have already done so for Common Lisp, for example. Someone correct me if I'm wrong, but I don't think `yield` adds any power to the language; it's only syntax sugar. If even [plain old Java](http://en.wikipedia.org/wiki/Generator_%28computer_science%29#Other_Implementations) can do generators, Clojure can.
Brian Carper
@Brian - That's a rather scary thing to say. `if` and `else` are "only syntax sugar." `yield` (like any other language construct) adds no _physical_ power to the language, but it adds a lot of _expressive_ power, which makes code clearer and more readable.
Chris Lutz
No, conditionals aren't syntax sugar. In Lisps at least, you need at least one primitive conditional to build others out of it, because it has non-standard evaluation rules (it short-circuits). In Clojure, `if` is one of (very few) primitives and `cond` is a macro built using it. But `cond` could've been the primitive in which case `if` could be build out of that. Or they both could be built on some other primitive. I think `yield` in a Lisp would be a function or macro that you could write yourself without the need for more primitives, that's all I meant.
Brian Carper
@Brian - Ah. I was assuming that `goto` is the basic primitive flow control statement (well, variations on `jmp` with different conditions). I suppose Lisps might have a hard time dealing with `goto`s and labels in the syntax, though.
Chris Lutz
+4  A: 

The wiki has an in depth treatment of various Fibonacci implementations in Clojure which may interest you if you haven't seen it already.

Timothy Pratley
+1  A: 

You should always use a language that fits the problem*. Your Python example is just lower level than the Clojure one -- easier to understand for beginners, but more tedious to write and parse for someone who knows the fitting higher level concepts.

* By the way, this also means that it is always nice to have a language that you can grow to fit.

Svante
A: 
(take 5 fibs)

Seems about as intuitive as it could possibly get. I mean, that is exactly what you're doing. You don't even need to understand anything about the language, or even know what language that is, in order to know what should happen.

nilamo
+5  A: 

The Clojure code is intuitive to me (because I know Clojure). If you want something that might look more like something you're familiar with, you can try this, an efficient and overly-verbose recursive version:

(use 'clojure.contrib.def)   ; SO's syntax-highlighting still sucks
(defn-memo fib [n]
  (cond (= n 0) 0
        (= n 1) 1
        :else   (+ (fib (- n 1))
                   (fib (- n 2)))))

(def natural-numbers (iterate inc 0))

(def all-fibs
  (for [n natural-numbers]
    (fib n)))

But to someone who doesn't know what recursion or memoization are, that isn't going to be intuitive either. The very idea of "infinite lazy sequences" probably isn't intuitive to the majority of programmers. I can't guess what's in your brain so I don't know what a more intuitive Clojure function would look like to you, other than "looks more like Python".

To someone who doesn't know programming, all of this stuff is going to look like gibberish. What's a loop? What's a function? What is this yield thing? That's where we all start. Intuitiveness is a function of what you've learned so far. Non-intuitive code is code you aren't familiar with yet. Extrapolating from "I know this" to "It's inherently more intuitive" is wrong.

Brian Carper
for me intuitive = closer to the mathematical definition.
Roskoto
@Roskoto - Under that logic, the Clojure version is clearer than the Python version, since the Clojure version is functional and closer to actual math than the procedural Python version.
Chris Lutz
+4  A: 

If you didn't know any imperative languages, would this be intuitive for you?

a = a + 5

WTF? a clearly isn't the same as a + 5.

if a = a + 5, is a + 5 = a?

Why doesn't this work???

if (a = 5) { // should be == in most programming languages
    // do something
}

There are a lot of things that aren't clear unless you've seen it before somewhere else and understood its purpose. For a long time I haven't known the yield keyword and in effect I couldn't figure out what it did.

You think the imperative approach is more legible because you are used to it.

Georg
+2  A: 

Here is one solution.

(defn fib-seq [a b]
  (cons (+ a b) (lazy-seq (fib-seq (+ a b) a))))

(def fibs (concat [1 1] (fib-seq 1 1)))

user=> (take 10 fibs)
(1 1 2 3 5 8 13 21 34 55)
Magnus Hedman
+1  A: 

I like:

(def fibs 
  (map first 
       (iterate 
           (fn [[ a, b       ]]  
                [ b, (+ a b) ]) 
           [0, 1])))     

Which seems to have some similarities to the python/generator version.

John Lawrence Aspden