Custom iterator failed

Here is very simple idea:

struct Test
  a::Int
  n::Int
end

step(x::Int)  =  x > 1 ? x - 1 : 1

Base.iterate(t::Test, state=1) = 
state > t.n ? nothing : (step(t.a), state + 1)

Base.length(t::Test)  =  t.n

For

some = Test(5, 3)
foreach(p->println(p), some)

I suppose to get 5, 4, 3
but code produce 4, 4, 4

What result do you expect? What is the code supposed to do?

t.a is always 5 (struct is immutable) so step always returns 4

So I should make mutable struct ?

Could you give a short explanation of what you would want the code in question to do? E.g. the output that you would expect.

This would make finding the issue much easier.

EDIT: I just saw you edited your post.

mutable struct Test
  a::Int
  n::Int
end

step(t::Test)  =  t.a > 1 ? (t.a -= 1) : 1

Base.iterate(t::Test, state=1) = 
state > t.n ? nothing : (step(t), state + 1)

Base.length(t::Test)  =  t.n

Like you suggested, T is now mutable und step now takes an argument of type Test and changes the value of t.a.

Does this work for you? (typing on the phone, so cannot check…)

It depends: do you want it to mutate? That is to say: do you want it to behave differently if you iterate over it twice? If so, you can make it mutable.

Otherwise, keep it immutable and let the state keep track of the iteration progress (after all, that’s what the “state” is meant to do).

I find it always better to have immutable structs, and delegate all state tracking to the iteration state (ie the second argument to iterate). Then one can always make the iterator stateful using Iterators.Stateful from Base, see Iteration utilities · The Julia Language

I saw many examples are immutable, and I prefer it!
But would you please explain, how to change this code without mutable?
In standard example from Iteration utilities there is no dependency from previous value

For example, you could structure the state as follows: initially

state = (t.a, 1)

Then iterate returns state[1], (state[1]-1, state[2]+1), or nothing if state[2] > t.n.

Edit: the idea is to keep in the state both the next value we want to return, and some other value that helps us decide when we need to stop.

An equivalent solution, that does not modify t could work along the lines of:

struct Test
  a::Int
  n::Int
end

step(x::Int, state)  =  x - state > 1 ? x - state : 1

Base.iterate(t::Test, state=1) = 
state > t.n ? nothing : (step(t.a,state), state + 1)

Base.length(t::Test)  =  t.n

No, no! I gave a simple example, working on a more complex design.
The next value depends only on the previous, does not depend on state!

Well, iterate does not get the previous value as input, but only the state. So the only way to achieve that, with an immutable iterator, is to store in the state everything you need to compute the next value. (This is true always, not just in this example)

1 Like

Well, looks like i do it

struct Test
  a::Int
  n::Int
end

step(x::Int)  =  x > 1 ? x - 1 : 1

function Base.iterate(t::Test)
  return (t.a, (t.a, 1))
end

function Base.iterate(t::Test, state) 
  next = step(state[1])
  state[2] >= t.n ? nothing : (next, (next, state[2] + 1))
end

Base.length(t::Test)  =  t.n


z = Test(5, 3)
for p in z
  println(p)
end

5, 4, 3

2 Likes