Nice post! I learned a couple of good tips from it.
In your “Slurping and Splatting” section, given that the docstring for iterate
does not discuss multiple state arguments, I do wonder a bit about the wisdom of creating a varargs iterate
—in my initial glance at the code, I wondered if it could be like getindex
and it might be of variable lengths maxing out at iter.interval
.
Here’s an alternative, where I pass an extra count
element indicating how many items are left:
function iterate(it::TakeNth)
xs_iter = @ifsomething iterate(it.xs)
return iterate(it, (it.interval-1, xs_iter)) # we already consumed 1 item, so count=it.interval-1
end
function iterate(it::TakeNth, (count, xs_iter))
for i = 1:count
xs_iter = @ifsomething iterate(it.xs, xs_iter[2])
end
return (xs_iter[1], (it.interval, xs_iter)) # next time, start over at the full count = it.interval
end
Admittedly it’s slightly more complicated, but aside from not making a questionable method this does have benefits:
master:
julia> using IterTools
julia> tn2 = takenth(10:20, 3)
IterTools.TakeNth{UnitRange{Int64}}(10:20, 0x0000000000000003)
julia> returnstrue(x) = true
returnstrue (generic function with 1 method)
julia> count(returnstrue, tn2)
3
julia> using BenchmarkTools
julia> @btime count($returnstrue, $tn2)
273.126 ns (12 allocations: 352 bytes)
3
This implementation:
julia> @btime count($returnstrue, $tn2)
37.008 ns (1 allocation: 32 bytes)
3
Also, one small correction regarding Base.tail
:
No matter the size of the input tuple, it will always return at least an empty tuple.
That’s true except for an empty tuple, which triggers an error. (which is a good thing)