Clojure return function after vs direct 'def' seq

Clojure newbie question. What are the pros and cons of the following two ways to implement / represent the Fibonacci sequence? (In particular, is there anything to rule out one or the other entirely as a bad idea.)

(ns clxp.fib
  (:gen-class))

; On the one hand, it seems more natural in code to have a function that
; returns 'a' Fibonacci sequence.
(defn fib-1
  "Returns an infinite sequence of Fibonnaci numbers."
  []
  (map first (iterate (fn [[a b]] [b (+ a b)]) [0 1])))

; > (take 10 (fib-1))
; (0 1 1 2 3 5 8 13 21 34)

; On the other hand, it seems more mathematically natural to define 'the'
; Fibonacci sequence once, and just refer to it.
(def fib-2
  "The infinite sequence of Fibonnaci numbers."
  (map first (iterate (fn [[a b]] [b (+ a b)]) [0 1])))

; > (take 10 fib-2)
; (0 1 1 2 3 5 8 13 21 34)

      

a) What are the pros and cons of these two approaches to defining an infinite sequence? (I know this is a somewhat special case in that this particular sequence does not require any arguments - as opposed to, say, an infinite sequence of multiples of "n", which I think would require the first approach to specify the value "n" .)

b) Is there any excessive reason to prefer one of these implementations over the other? (Memory consumption, applicability when used as an argument, etc.)

+3


source to share


2 answers


fib-2

is preferred in favor of time performance if its elements are scanned multiple times, because in lazy seq they only need to be evaluated once.



Because of the global binding, seq is unlikely to ever become garbage collected, so if your program will go through a million fibonacci to do some calculations once, even more so if it doesn't need to hold the seqs section, referencing fib-1

in a local context is preferable to space performance.

+3


source


It depends on your use, and how important it is not to digest fib seq multiple times. However, from my experiments below, I had problems with def when using long sequences.

If you're going to be referencing a lot of elements then you'll need to watch out for head retention, as Leon pointed out.

This can be illustrated as follows (they extend a few examples from Clojure Programming):

(let [[t d] (split-with #(< % 10) (take 1e6 (fib-1)))]
  [(count d) (count t)])
=> OutOfMemoryError Java heap space

(let [[t d] (split-with #(< % 10) (take 1e6 (fib-1)))]
  [(count t) (count d)])
=> [7 999993]

      

Please note, I had to change your implementation to use a start vector [0 1N]

to avoid ArithmeticException integer overflow

doing large sequences of fib numbers.



Interestingly, switching to using fib-2 instead gives the same OOM error for storing the head unit, but the incontinence version breaks:

(let [[t d] (split-with #(< % 10) (take 1e6 fib-2))]
  [(count t) (count d)])
=> [7 270036]

      

The last reading should be 999993.

The reason for the OOM in both cases is as stated in Clojure Programming:

Since the last call of t occurs before d is processed, no reference to the head of the range sequence is retained and no memory issues arise.

+2


source







All Articles