Implementation of itertools.count


#1

I’m trying to emulate python’s itertools.count. Seems like it should be simple, but I’m having trouble with it:

struct count
    n:: Int64
end

import Base: start, next, done
function start(c::count)
    return c.n
end

function next(c::count, i)
    return i+1, i+1
end

done(c::count, i) = false
julia> [i for i in take(count(1), 10)]
WARNING: Base.take is deprecated.
  likely near no file:0
ERROR: MethodError: no method matching length(::count)
Closest candidates are:
  length(!Matched::SimpleVector) at essentials.jl:257
  length(!Matched::Base.MethodList) at reflection.jl:558
  length(!Matched::MethodTable) at reflection.jl:634
  ...
Stacktrace:
 [1] _min_length(::count, ::UnitRange{Int64}, ::Base.HasLength, ::Base.HasLength) at ./iterators.jl:14
 [2] _array_for at ./array.jl:416 [inlined]
 [3] collect(::Base.Generator{Base.Iterators.Take{count},##3#4}) at ./array.jl:430
 [4] macro expansion at ./REPL.jl:97 [inlined]
 [5] (::Base.REPL.##1#2{Base.REPL.REPLBackend})() at ./event.jl:73

#2

Base.iteratorsize(::Type{count}) = Base.SizeUnknown()

https://docs.julialang.org/en/stable/manual/interfaces/


#3

Note Base.Iterators.countfrom. If you want to reimplement it (for learning), look at its source, and the iteration interface, particularly iteratorsize.


#4

Is there any case in which you couldn’t use a UnitRange object for this? These can be constructed with start:interval:stop or simply start:stop (yes, the fact that a colon serves as a constructor for these as well as everything else it does in Julia is confusing).


#5

What else does a colon do?


#6

Colons have the following uses in Julia:

  • To declare a Symbol like :symbol. (A Symbol is an interned string, or rather a string that gets tokenized by the compiler.)
  • As a constructor for ranges like 1:10 or 1.0:0.5:5.0. These are most commonly used for slicing arrays, but are general iterators.
  • In implication constructs rand(Bool) ? :win : :lose.
  • Double colons are used in type assertions :x::Symbol.
  • A colon is also its own object: a singleton instance of the Colon type. This is often used as an argument for slicing arrays, or, for example, a dataframe df[:, (rand(Bool) ? :c1 : :c2)::Symbol]

(These examples were deliberately silly.)

Yeah… honestly this is the one major thing that bugs me about the Julia syntax. It certainly makes sense to use it for slicing and conditional constructs and the double colons aren’t so bad because those are all things that are fairly common in other languages. I really wish they had used something else for the symbols though, like maybe %. Too late now. Obviously it’s all logically consistent, or it wouldn’t be possible to compile, it’s just a bit ugly to look at in some cases


#7

Oh, thanks! I missed Base.Iterators.countfrom


#8

Great list, thanks! Is there somewhere in the Julia docs, or maybe the StackOverflow Julia docs, that this could be added?


#9

Well, all of those things are certainly extensively documented. I don’t believe there’s a place that compiles all the uses of : specifically. In a way that makes sense: :symbol, :, 1:3 and x::Int all have completely different, even unrelated semantics, so they don’t really bear any relation to each other other than that they all happen to have the character :. I’d think the proper thing to do would be to simply search for :, although I don’t think the documentation search is quite that good.


#10

Agreed. But maybe it would be useful to have a summary page somewhere on “use of punctuation symbols in Julia”, where each use of each symbol is succintly listed in one place, as you exemplified.


#11

You mean the (rather under-advertised) punctuation page?


#12

Ah yes, thanks!


#13

Shouldn’t this be a fallback (i.e. default) for any type that hasn’t told us that it has known size?


#14

I was thinking the same thing but perhaps it is a performance trap?


#15

Two options then:

  1. Make SizeUnknown the fallback so it works by default but may be slower than optimal.
  2. Improve the error when length fails and iteratorsize is SizeUnknown indicating that the user needs to either change iteratorsize or define length.

This is why we need interfaces/protocols.


#16

1 is what I meant with a performance trap. I would say most iterators have known length. 2 seems like a good idea.