I don't get Base.IteratorElType

nsajko
Mon, 29 Apr 2024 08:22:38 +0000
Internals & Design

Motivation

I’m implementing a collect-like function for a certain subtype of AbstractArray, FixedSizeArray (@giordano). The function is supposed to take an arbitrary iterator, consume it, and return a FixedSizeArray value constructed from the iterator. While trying to do this, however, I realized I don’t understand anything about Base.IteratorElType.

Base.IteratorElType

What goals is Base.IteratorElType supposed to accomplish? What does it solve? What are its semantics in practice?

Empirically, the only place where I’ve found that Base.IteratorElType matters is for collect:

julia> it = Ref{Any}(7)
Base.RefValue{Any}(7)

julia> Base.IteratorEltype(it)
Base.HasEltype()

julia> collect(it)
0-dimensional Array{Any, 0}:
7

julia> map(identity, it)
0-dimensional Array{Int64, 0}:
7

julia> typeof(it)
Base.RefValue{Any}

julia> Base.IteratorEltype(::Type{Base.RefValue{Any}}) = Base.EltypeUnknown()

julia> Base.IteratorEltype(it)
Base.EltypeUnknown()

julia> collect(it)
0-dimensional Array{Int64, 0}:
7

julia> map(identity, it)
0-dimensional Array{Int64, 0}:
7

So Base.IteratorElType affects the behavior of collect, while it does not affect map. The effect is that setting the return value to Base.EltypeUnknown() makes collect behave more like map, otherwise it just preserves the element type exactly.

Is this really the only place where Base.IteratorElType matters, for collect? Is the current behavior even desirable? I guess returning Array{Int} would actually be nicer than returning Array{Any}?

Questions regarding the design of the new function

Basically I wonder if bothering with Base.IteratorElType while implementing the new function is even necessary.

This is the interface I imagine so far:

"""
    collect_as(t::Type{<:FixedSizeArray}, iterator)

Tries to construct a value of type `t` from the iterator `iterator`. The type `t`
must either be concrete, or a `UnionAll` without constraints.
"""
function collect_as(::Type{T}, iterator) where {T<:FixedSizeArray}
    ...
end

My intention is basically to have collect_as behave like so:

1. If the element type is given, like, for example, in collect_as(FixedSizeArray{Int}, iter), where the element type is given as Int, the returned value must have the requested element type. So Base.IteratorElType doesn’t need to come into the picture.

2. If no element type is requested, like, for example, in collect_as(FixedSizeArray, iter), the intention is to ignore Base.IteratorElType, store the elements of the iterator into a temporary FixedSizeArray value whose element type is eltype(iter), then, at the end, return map(identity, temporary_fixed_size_array), so as to get a tight element type. So I’m ignoring Base.IteratorElType, is that OK?

7 posts - 4 participants