How to implement an iterator in Julia?
Asked Answered
V

3

6

I am trying to implement an iterator in Julia, but get an exception when the for-loop tries to call start already.

Here is what I get (I ran include(...), then using RDF):

julia> methods(start)
# 1 method for generic function "start":
start(graph::Graph) at /Users/jbaran/src/RDF.jl/src/RDF.jl:214

julia> for x in g
       println(x)
       end
ERROR: `start` has no method matching start(::Graph)
 in anonymous at no file

The function definition in the RDF module looks like this at the moment:

function start(graph::Graph)
    return GraphIterator(collect(keys(graph.statements)), nothing, nothing, nothing, [], [])
end

Any idea what I am doing wrong?

Verile answered 30/7, 2014 at 3:58 Comment(0)
B
9

Don't forget to specify Base. - you are adding methods to an existing function.

module MyMod
  type Blah
    data
  end
  export Blah
  Base.start(b::Blah) = 1
  Base.done(b::Blah,state) = length(b.data) == state-1
  Base.next(b::Blah,state) = b.data[state], state+1
end
using MyMod
x = Blah([1,2,3])
for i in x
  println(i)
end

This works as of Julia 0.3.

Braided answered 30/7, 2014 at 4:41 Comment(3)
Ah. Adding "Base." to start, done and next did it. I will need to look once more into scoping. Thank you very much!Verile
If you don't want to have to prefix Base. in front of every method you define, you can explicitly import it via import Base: start, done next at the top of your module.Attorney
It is good practice to prefix Base to prevent collisions.Assurance
A
11

In Julia 1.+, you should implement:

  1. Base.iterate(::YourType) for the starting iteration,
  2. Base.iterate(::YourType, state) for following iterations while getting the state from the previous steps.

Both methods should return (result, state) tuple, except for the last iteration that should return nothing.

In practice, this means that iterating on x::YourType with

for i in x
    # some code
end

is a shorthand for writing

it = iterate(x)
while it !== nothing
    i, state = it
    # some code
    it = iterate(x, state)
end

See the manual for details.

Arianna answered 18/10, 2020 at 23:21 Comment(0)
B
9

Don't forget to specify Base. - you are adding methods to an existing function.

module MyMod
  type Blah
    data
  end
  export Blah
  Base.start(b::Blah) = 1
  Base.done(b::Blah,state) = length(b.data) == state-1
  Base.next(b::Blah,state) = b.data[state], state+1
end
using MyMod
x = Blah([1,2,3])
for i in x
  println(i)
end

This works as of Julia 0.3.

Braided answered 30/7, 2014 at 4:41 Comment(3)
Ah. Adding "Base." to start, done and next did it. I will need to look once more into scoping. Thank you very much!Verile
If you don't want to have to prefix Base. in front of every method you define, you can explicitly import it via import Base: start, done next at the top of your module.Attorney
It is good practice to prefix Base to prevent collisions.Assurance
S
2

Building off of @BoZenKhaa's answer, a common pattern is

Base.iterate(yt::YourType, state=1) = state > length(yt) ? nothing : (yt.data[state],state+1)

where YourType has a field data which can be indexed as an array, with state holding the index. Along with state=1, the right hand side uses the ternary operator to combine lines 1+2, returning nothing if at the end or if in bounds a tuple of the element at that state and the next state.

Sarto answered 13/10, 2023 at 21:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.