celeritas
celeritas

Reputation: 388

Why filter on a lazy sequence doesn't work in clojure?

I am hoping to generate all the multiples of two less than 10 using the following code

(filter #(< % 10) (iterate (partial + 2) 2))

Expected output:

(2 4 6 8)

However, for some reason repl just doesn't give any output?

But, the below code works just fine...

(filter #(< % 10) '(2 4 6 8 10 12 14 16))

I understand one is lazy sequence and one is a regular sequence. That's the reason. But how can I overcome this issue if I wish to filter all the number less than 10 from a lazy sequence...?

Upvotes: 7

Views: 1552

Answers (2)

Diego Basch
Diego Basch

Reputation: 13079

(iterate (partial + 2) 2)

is an infinite sequence. filter has no way to know that the number of items for which the predicate is true is finite, so it will keep going forever when you're realizing the sequence (see Mark's answer).

What you want is:

(take-while #(< % 10) (iterate (partial + 2) 2))

Upvotes: 10

Mark Karpov
Mark Karpov

Reputation: 7599

I think I should note that Diego Basch's answer is not fully correct in its argumentation:

filter has no way to know that the number of items for which the predicate is true is finite, so it will keep going forever

Why should filter know something about that? Actually filter works fine in this case. One can apply filter on a lazy sequence and get another lazy sequence that represent potentially infinite sequence of filtered numbers:

user> (def my-seq (iterate (partial + 2) 2)) ; REPL won't be able to print this
;; => #'user/my-seq
user> (def filtered (filter #(< % 10) my-seq)) ; filter it without problems
;; => #'user/filtered
user> 

Crucial detail here is that one should never try to realize (by printing in OP's case) lazy sequence when actual sequence is not finite for sure (so that Clojure knows that).

Of course, this example is only for demonstration purposes, you should use take-while here, not filter.

Upvotes: 6

Related Questions