Reputation: 903
A popular online tutorial gives this example to build natural numbers:
(def infseq (map inc (iterate inc 0)))
For instance, (take 5 infseq)
gives:
1 2 3 4 5
I can see what (iterate inc 0)
does, but, as a whole, I do not understand what exactly is going on. (For example, this is not a normal function definition.)
Could somebody please explain?
Upvotes: 1
Views: 498
Reputation: 13473
. For the purposes of the example, we could define map
and iterate
as follows:
(defn iterate [f init]
(lazy-cons init (iterate f (f init))))
(defn map [f [x & xs]]
(lazy-cons (f x) (map f xs)))
... where lazy-cons
is a version of cons
that doesn't act until it has to. It used to be part of clojure.core
, and might have been defined thus:
(defmacro lazy-cons [x xs]
`(lazy-seq (cons ~x ~xs)))
To understand these definitions, you'll need to get to grips with recursion, laziness, destructuring, and macros: quite a task! But do so and you'll really understand how clojure's sequence library, including iterate
and map
, works. It's how I learned.
The definition of iterate
is valid. The one of map
only works for a single endless sequence argument.
Upvotes: 2
Reputation: 91534
Let's break that expression down:
(map inc (iterate inc 0)))
Is a list (the datastructure) with this structure:
(function-to-call function-passed-as-first-srgument another-list-as-second-arg)
Now let's explore it by examining it from the inside out!
That inner list:
(iterate inc 0)
has this strucute
(function-to-call function-passed-as-first-argument number)
iterate
, which creates infinite sequences by keeping track of it's internal state, and every time a new value is required to make the make the sequence longer, it takes the function passed as the first argument and calls it on the current state. inc
which takes a numbr and ads oneiterate
is the initial-state. where it should start.so when this inner expression is evaluated it will immediately return a data structure representing a list without actually building that list yet. When the first value is read from that list it will return the initial value 0
, when the second value is asked for it will use the inc
function to come up with the number 1
. If the first or second values are needed again they will just be used as is, not recalculated.
so the first argument represents a contract to produce as many numbers as are required. This is in it's self the third argument to the original expression.
The initial expression takes that lazy list and makes a new lazy list.
this new lazy list is returned by the map
function.
(map inc (0 1 2 3 4 ... as many as you read ...))
which will apply inc
to each of these just as it's read, and only at the moment it's read (actually it caches 20 or so items ahead to be a little faster) resulting in this sequence:
((inc 0) (inc 1) (inc 2) (inc 3) ... as much as you read from the sequence ...)
Which works out to:
(1 2 3 4 ... created lazily)
Which is the same result as these equivalent expressions,
(rest (range))
(iterate inc 1)
and many other forms.
Upvotes: 2
Reputation: 29958
(take 5 (iterate inc 0)) => (0 1 2 3 4)
iterate
repeatedly applys the inc function in a loop. You are starting with 0
, so you get [0 1 2 3...]
(take 5 (map inc (iterate inc 0))) => (1 2 3 4 5)
(map inc <collection>)
applies inc
once time to each item in the collection, to the previous result is transformed to [1 2 3 4 ...]
(take 5 (range)) => (0 1 2 3 4)
range
w/o any args starts at 0 and counts forever, same as the first example.
Since all of these collections are infinite in length, we need something like (take 5 <collection>)
to limit the length of what is printed.
Upvotes: 1