Tee

Python iterators and Haskell lists are different things. We have not yet had to reckon with this fact, but for this lesson we can no longer look past the distinction.

Python iterators are mutable

First we’ll set up an example to illustrate why itertools.tee exists. Let’s say we start with an iterator that represents all of the natural numbers starting from 1:

>>> it = count(1)

And then we define two more iterators based on it; one that filters to get only the evens, and another that filters to get only the odds.

>>> it_even = filter(lambda x: x % 2 == 0, it)
>>> it_odd  = filter(lambda x: x % 2 == 1, it)

If we sample the first five evens and the first five odds, we might hope to get 2,4,6,8,10 for the evens and 1,3,5,7,9 for the odds. But this isn’t what happens:

>>> list(islice(it_even, 5))
[2, 4, 6, 8, 10]

>>> list(islice(it_odd, 5))
[11, 13, 15, 17, 19]

Why not? Because it isn’t really a representation of all the natural numbers starting from 1. It’s more like a mutable database of numbers, and each time something interacts with it, it changes. When we first create it, it’s a sequence starting from 1.

>>> it = count(1)

But as soon as we apply next to it, it’s now a sequence starting from two.

>>> next(it)
1

And we can observe the change because, the next time we interact with it, something different happens.

>>> next(it)
2

Haskell lists are immutable

Haskell lists don’t work like this. If we define a list starting at one,

λ> xs = [1..]

then the first element in the list is 1, and it is always 1 no matter how many times we ask.

λ> head xs
1

λ> head xs
1

So when we try the even-and-odd experiment again, we get the expected result.

λ> xsEven = filter even xs
λ> xsOdd = filter odd xs

λ> take 5 xsEven
[2,4,6,8,10]

λ> take 5 xsOdd
[1,3,5,7,9]

tee

For Python iterators, itertools.tee itertools.tee is there to deal with this issue. We can use tee to fork the original iterator into two copies that each have their own separate internal state.

>>> it = count(1)
>>> it_1, it_2 = tee(it, 2)

it_1 and it_2 are independent; we can use one without affecting the other.

>>> it_even = filter(lambda x: x % 2 == 0, it_1)
>>> it_odd = filter(lambda x: x % 2 == 1, it_2)

>>> list(islice(it_even, 5))
[2, 4, 6, 8, 10]

>>> list(islice(it_odd, 5))
[1, 3, 5, 7, 9]

This is the last of our lessons on itertools functions. Next we’ll move onto other topics!

Join Type Classes for courses and projects to get you started and make you an expert in FP with Haskell.