From 93c4533d92172610c85304548c558b266659de71 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Wed, 17 Jun 2020 23:07:21 -0700 Subject: [PATCH 1/7] Add into(T::Type, iterable) -> collection::T --- base/Base.jl | 3 + base/collections.jl | 201 ++++++++++++++++++++++++++++++++++++ base/exports.jl | 1 + base/idset.jl | 1 + doc/src/base/collections.md | 2 + test/choosetests.jl | 3 +- test/collections.jl | 103 ++++++++++++++++++ 7 files changed, 313 insertions(+), 1 deletion(-) create mode 100644 base/collections.jl create mode 100644 test/collections.jl diff --git a/base/Base.jl b/base/Base.jl index b9821b6856324..10d4efd4d5975 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -328,6 +328,9 @@ include("download.jl") include("summarysize.jl") include("errorshow.jl") +# Collections API +include("collections.jl") + # Stack frames and traces include("stacktraces.jl") using .StackTraces diff --git a/base/collections.jl b/base/collections.jl new file mode 100644 index 0000000000000..1bf818d33a1dd --- /dev/null +++ b/base/collections.jl @@ -0,0 +1,201 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +""" + into(T::Type, iterable) -> collection::T + into(T::Type) -> iterable -> collection::T + +Construct a new `collection` of type `T` that contains the elements in `iterable`. If +`iterable` is also a container, it acts as a shallow-copy. + +If `T` has `eltype`, `keytype`, or `valtype` information, all elements in `collection` are +converted to the destination type to guarantee the constraint `collection isa T`. +Otherwise, if `IteratorEltype(collection)` is `HasEltype()` and type `T` can specify element +type in the type domain, `eltype(typeof(collection)) == eltype(typeof(iterable))` holds. + +If `T` has size or length information (e.g., `NTuple` and `StaticArray`), providing +`collection` with unmatched size or length throws an error. + +Unary form `into(T::Type)` returns a callable `iterable -> into(T, iterable)`. + +# Extended help + +## Example + +`into` takes care of the conversion of storage and element types: + +```jldoctest +julia> into(Array{Int}, BitVector([0, 1, 0, 0])) +4-element Array{Int64,1}: + 0 + 1 + 0 + 0 +``` + +`into` acts like a shallow copy: + +```jldoctest +julia> xs = Ref.([1, 2, 3]); + +julia> ys = into(Vector, xs) +3-element Array{Base.RefValue{Int64},1}: + Base.RefValue{Int64}(1) + Base.RefValue{Int64}(2) + Base.RefValue{Int64}(3) + +julia> ys[1] = Ref(100); + +julia> xs +3-element Array{Base.RefValue{Int64},1}: + Base.RefValue{Int64}(1) + Base.RefValue{Int64}(2) + Base.RefValue{Int64}(3) + +julia> ys[2][] = 200; + +julia> xs +3-element Array{Base.RefValue{Int64},1}: + Base.RefValue{Int64}(1) + Base.RefValue{Int64}(200) + Base.RefValue{Int64}(3) +``` + +`into` _always_ treats input `iterable` as a collection: + +```jldoctest +julia> into(Dict, (:a => 1) => (:b => 2)) +Dict{Symbol,Int64} with 2 entries: + :a => 1 + :b => 2 + +julia> Dict((:a => 1) => (:b => 2)) # but the constructor may not +Dict{Pair{Symbol,Int64},Pair{Symbol,Int64}} with 1 entry: + :a=>1 => :b=>2 +``` + +`into(T)` returns a function `iterable -> into(T, iterable)` which is +appropriate for using with [`|>`](@ref): + +```jldoctest +julia> 1:3 |> into(NTuple{3}) +(1, 2, 3) +``` + +## Implementation + +The owner of the type of `iterable` should implement [`__from__`](@ref __into__). +The owner of the output type `T` should implement [`__into__`](@ref). If it is desirable to +apply pre- and/or post-processing, the owner of the output type `T` may implement `into`. +""" +into(::Type{T}, iterable) where {T} = __from__(T, iterable)::T +into(::Type{T}) where {T} = Fix1(into, T) + +function __into__ end +__from__(::Type{T}, iterable) where {T} = __into__(T, iterable) + +""" + Base.__into__(T::Type, iterable) -> collection::T + Base.__from__(T::Type, iterable) -> collection::T + +Overload-only API for providing the implementation for `into(T, iterable)`. + +To avoid method ambiguities, `__into__` should be implemented only by the owner of `T` and +`__from__` should be implemented only by the owner of `typeof(iterable)`. The owner of +`T` (resp. `typeof(iterable)`) may choose to allow `typeof(iterable)` (resp. `T`) to +overload `__into__` (resp. `__from__`) by documenting specific type bounds for +`T` (resp. `typeof(iterable)`). + +`into(T, iterable)` calls `__from__(T, iterable)` which in turn by default calls +`__into__(T, iterable)`. + +## Implementation + +If `IteratorEltype(collection)` is `HasEltype()` and type `T` can specify element type in +the type domain, `eltype(typeof(collection)) == eltype(typeof(iterable))` must hold. + +If `T` is a subtype of `AbstractArray`, + +```julia +isequal(vec(collect′(iterable)), vec(collect(collection))) +``` + +must hold where `collect′` is defined as + +```julia +collect′(iterable) = + if IteratorEltype(collection) isa HasEltype + collect(eltype(collection), iterable) + else + collect(iterable) + end +``` + +If `iterable` is a stateful iterator, `collect` inside `collect′` is "run" as if the world +is rolled back to the state just before calling `into`. + +If the collections of type `T` do not support duplicates, `issubset` may be used instead of +`isequal`. In particular, subtypes of `AbstractDict` and `AbstractSet` must satisfy the +above equality with `issubset`. + +If the collections of type `T` do not maintain insertion order, `issetequal` may be used +instead of `isequal`. +""" +(__from__, __into__) + +__into__(::Type{T}, iterable) where {T<:Array} = collect(iterable) +__into__(::Type{T}, iterable) where {E,T<:Array{E}} = collect(E, iterable) + +__into__(::Type{Vector}, iterable) = vec(collect(iterable))::Vector +__into__(::Type{T}, iterable) where {E,T<:Vector{E}} = vec(collect(E, iterable)) + +__into__(::Type{T}, iterable) where {T<:BitArray} = T(iterable) + +__into__(::Type{T}, iterable) where {T<:Union{Dict,IdDict}} = T(iterable) +__into__(::Type{T}, (a, b)::Pair) where {T<:Union{Dict,IdDict}} = T((a, b)) + +__into__(::Type{T}, iterable) where {T<:ImmutableDict} = foldl(T, iterable) + +__into__(::Type{T}, iterable) where {T<:Union{Set,IdSet}} = T(iterable) + +@noinline _too_many_items_error(N, x) = throw(ArgumentError( + "`iterable` contains more than $N element(s);" * + " $(N+1)-th element is `$x`" +)) + +@noinline _not_enough_items_error(N, n_actual) = throw(ArgumentError( + "$N items required; `iterable` contains only $n_actual element(s)" +)) + +function __into__(::Type{Tuple{}}, iterable) + y = iterate(iterable) + y === nothing && return () + _too_many_items_error(0, y[1]) +end + +function __into__(::Type{NTuple{N,Any}}, iterable) where {N} + y = iterate(iterable) + y === nothing && _not_enough_items_error(N, 0) + x, state = y + collection, state = _foldoneto(((x,), state), Val(N - 1)) do (acc, state), i + local y + y = iterate(iterable, state) + y === nothing && _not_enough_items_error(N, i) + ((acc..., y[1]), y[2]) + end + y = iterate(iterable, state) + y === nothing && return collection + _too_many_items_error(N, y[1]) +end + +_tuple_length(::Type{<:NTuple{N,Any}}) where {N} = N + +__into__(::Type{T}, iterable) where {T <: Tuple} = + convert(T, __into__(NTuple{_tuple_length(T),Any}, iterable)) + +function __into__(::Type{NTuple{<:Any,T}}, iterable) where {T} + collection = Tuple(iterable) + return convert(NTuple{length(collection),T}, collection) +end + +__into__(::Type{NTuple{N}}, iterable) where {N} = + promote(__into__(NTuple{N,Any}, iterable)...) diff --git a/base/exports.jl b/base/exports.jl index d695714665aab..7a1f3363453a7 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -510,6 +510,7 @@ export in, intersect!, intersect, + into, isdisjoint, isempty, issubset, diff --git a/base/idset.jl b/base/idset.jl index cec8ed96caff8..2156a9ff67d04 100644 --- a/base/idset.jl +++ b/base/idset.jl @@ -8,6 +8,7 @@ end IdSet{T}(itr) where {T} = union!(IdSet{T}(), itr) IdSet() = IdSet{Any}() +IdSet(itr) = IdSet{Any}(itr) copymutable(s::IdSet) = typeof(s)(s) copy(s::IdSet) = typeof(s)(s) diff --git a/doc/src/base/collections.md b/doc/src/base/collections.md index 383dbcda4f93e..5e5826937a99f 100644 --- a/doc/src/base/collections.md +++ b/doc/src/base/collections.md @@ -133,6 +133,8 @@ Base.tail Base.step Base.collect(::Any) Base.collect(::Type, ::Any) +Base.into +Base.__into__ Base.filter Base.filter! Base.replace(::Any, ::Pair...) diff --git a/test/choosetests.jl b/test/choosetests.jl index 8e8b4ae87ed6d..53f0d5782d99c 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -54,7 +54,8 @@ function choosetests(choices = []) "checked", "bitset", "floatfuncs", "precompile", "boundscheck", "error", "ambiguous", "cartesian", "osutils", "channels", "iostream", "secretbuffer", "specificity", - "reinterpretarray", "syntax", "logging", "missing", "asyncmap", "atexit" + "reinterpretarray", "syntax", "logging", "missing", "asyncmap", "atexit", + "collections", ] tests = [] diff --git a/test/collections.jl b/test/collections.jl new file mode 100644 index 0000000000000..1859f7762b2a2 --- /dev/null +++ b/test/collections.jl @@ -0,0 +1,103 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +@testset "into(Array, ::Vector)" begin + xs = [1, 2, 3] + @testset for T in [Array, Array{Float64}, Vector, Vector{Float64}] + @test into(T, xs) isa T + @test into(T, xs) !== xs + @test into(T, xs) == xs + end +end + +@testset "into(Array, ::Matrix)" begin + xs = [1 3; 2 4] + @testset for T in [Array, Array{Float64}, Matrix, Matrix{Float64}] + @test into(T, xs) isa T + @test into(T, xs) !== xs + @test into(T, xs) == xs + end + @testset for T in [Vector, Vector{Float64}] + @test into(T, xs) isa T + @test into(T, xs) !== vec(xs) + @test into(T, xs) == vec(xs) + end +end + +@testset "into(BitArray, _)" begin + @testset for T in [BitArray, BitVector] + xs = BitArray([false, false]) + ys = into(T, xs) + @test ys isa T + @test ys == xs + @testset "shallow copy" begin + @test ys !== xs + ys[1] = true + @test xs[1] == false + end + end +end + +@testset "into(AbstractDict, _)" begin + @testset for T in [Dict, Base.IdDict, Base.ImmutableDict] + xs = [:a => 1, :b => 2, :c => 3] + @test issetequal(collect(into(T, copy(xs))::T), xs) + end +end + +@testset "into(AbstractDict, ::Pair)" begin + @testset for T in [Dict, Base.IdDict, Base.ImmutableDict] + @test issetequal(collect(into(T, (:a => 1) => (:b => 2))::T), [:a => 1, :b => 2]) + end +end + +@testset "into(AbstractSet, ::Array)" begin + @testset for T in [Set, Base.IdSet] + xs = [1, 2, 2] + @test into(T, xs) isa T + @test into(T, xs) == Set([1, 2]) + end +end + +@testset "into(AbstractSet, ::Set)" begin + @testset for T in [Set, Base.IdSet] + xs = Set([1, 2]) + @test into(T, xs) isa T + @test into(T, xs) == xs + @testset "shallow copy" begin + push!(into(T, xs), 3) + @test xs == Set([1, 2]) + end + end +end + +@testset "into(Tuple, _)" begin + @test @inferred(into(Tuple{}, 1:0)) === () + + @test @inferred(into(NTuple{2,Int}, [10, 20])) === (10, 20) + @test @inferred(into(NTuple{2}, [10, 20])) === (10, 20) + @test @inferred(into(NTuple{2,Float64}, [10, 20])) === (10.0, 20.0) + @test into(NTuple{3}, (1, 2.0, 3im)) === (1.0 + 0.0im, 2.0 + 0.0im, 0.0 + 3.0im) + @test @inferred(into(Tuple{Int,Float64}, 1:2)) === (1, 2.0) + @test into(NTuple{<:Any,Float64}, 1:3) === (1.0, 2.0, 3.0) + + @test @inferred(into(NTuple{2,Int})([10, 20])) === (10, 20) + @test @inferred(into(NTuple{2})([10, 20])) === (10, 20) + @test @inferred(into(NTuple{2,Float64})([10, 20])) === (10.0, 20.0) + + @test_throws( + ArgumentError("`iterable` contains more than 0 element(s); 1-th element is `1`"), + into(Tuple{}, 1:2) + ) + @test_throws( + ArgumentError("`iterable` contains more than 3 element(s); 4-th element is `40`"), + into(NTuple{3}, 10:10:40) + ) + @test_throws( + ArgumentError("3 items required; `iterable` contains only 0 element(s)"), + into(NTuple{3}, 1:0) + ) + @test_throws( + ArgumentError("3 items required; `iterable` contains only 2 element(s)"), + into(NTuple{3}, 1:2) + ) +end From 4c2a999558b6203b99e36144ff9516c0be972239 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 4 Jul 2020 15:22:02 -0700 Subject: [PATCH 2/7] Fix type instability of Fix1 and Fix2 when capturing type as an argument This is required for inferring `Fix1(convert, Int)(x)`. --- base/operators.jl | 15 +++++++++------ test/operators.jl | 8 ++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index c8e44e7c816eb..302d9c32f3b9d 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -913,6 +913,9 @@ julia> filter(!isletter, str) """ !(f::Function) = (x...)->!f(x...) +_typeof(::T) where {T} = T +_typeof(::Type{T}) where {T} = Type{T} + """ Fix1(f, x) @@ -923,11 +926,11 @@ A type representing a partially-applied version of the two-argument function struct Fix1{F,T} <: Function f::F x::T - - Fix1(f::F, x::T) where {F,T} = new{F,T}(f, x) - Fix1(f::Type{F}, x::T) where {F,T} = new{Type{F},T}(f, x) + Fix1{F,T}(f::F, x::T) where {F,T} = new{F,T}(f, x) end +Fix1(f::F, x::T) where {F,T} = Fix1{_typeof(f),_typeof(x)}(f, x) + (f::Fix1)(y) = f.f(f.x, y) """ @@ -940,11 +943,11 @@ A type representing a partially-applied version of the two-argument function struct Fix2{F,T} <: Function f::F x::T - - Fix2(f::F, x::T) where {F,T} = new{F,T}(f, x) - Fix2(f::Type{F}, x::T) where {F,T} = new{Type{F},T}(f, x) + Fix2{F,T}(f::F, x::T) where {F,T} = new{F,T}(f, x) end +Fix2(f::F, x::T) where {F,T} = Fix2{_typeof(f),_typeof(x)}(f, x) + (f::Fix2)(y) = f.f(y, f.x) """ diff --git a/test/operators.jl b/test/operators.jl index fb0af5b552047..4952f81b236a9 100644 --- a/test/operators.jl +++ b/test/operators.jl @@ -226,6 +226,14 @@ end fy = Base.Fix2(/, y) @test fx(y) == x / y @test fy(x) == x / y + + convert_Int = Base.Fix1(convert, Int) + @test convert_Int isa Base.Fix1{typeof(convert),Type{Int}} + @test @inferred(convert_Int(1.0)) === 1 + + read_String = Base.Fix2(read, String) + @test read_String isa Base.Fix2{typeof(read),Type{String}} + @test @inferred(read_String(devnull)) === "" end @testset "curried comparisons" begin From 424c782039544151a0ccb4db3d2ecf66dd30fa46 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 4 Jul 2020 16:21:56 -0700 Subject: [PATCH 3/7] Fix doctests --- base/collections.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/base/collections.jl b/base/collections.jl index 1bf818d33a1dd..fa64ef153b90c 100644 --- a/base/collections.jl +++ b/base/collections.jl @@ -25,7 +25,7 @@ Unary form `into(T::Type)` returns a callable `iterable -> into(T, iterable)`. ```jldoctest julia> into(Array{Int}, BitVector([0, 1, 0, 0])) -4-element Array{Int64,1}: +4-element Vector{Int64}: 0 1 0 @@ -38,7 +38,7 @@ julia> into(Array{Int}, BitVector([0, 1, 0, 0])) julia> xs = Ref.([1, 2, 3]); julia> ys = into(Vector, xs) -3-element Array{Base.RefValue{Int64},1}: +3-element Vector{Base.RefValue{Int64}}: Base.RefValue{Int64}(1) Base.RefValue{Int64}(2) Base.RefValue{Int64}(3) @@ -46,7 +46,7 @@ julia> ys = into(Vector, xs) julia> ys[1] = Ref(100); julia> xs -3-element Array{Base.RefValue{Int64},1}: +3-element Vector{Base.RefValue{Int64}}: Base.RefValue{Int64}(1) Base.RefValue{Int64}(2) Base.RefValue{Int64}(3) @@ -54,7 +54,7 @@ julia> xs julia> ys[2][] = 200; julia> xs -3-element Array{Base.RefValue{Int64},1}: +3-element Vector{Base.RefValue{Int64}}: Base.RefValue{Int64}(1) Base.RefValue{Int64}(200) Base.RefValue{Int64}(3) From 321f9b89c7ed6409e20f6902401e0d07c0df4762 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 4 Jul 2020 19:37:06 -0700 Subject: [PATCH 4/7] Exclude an `__into__` method from undef sparam check --- test/ambiguous.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/ambiguous.jl b/test/ambiguous.jl index c9f2d74d9fbdb..be814122a95a9 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -320,6 +320,7 @@ end pop!(need_to_handle_undef_sparam, which(Base.oneunit, Tuple{Type{Union{Missing, T}} where T})) pop!(need_to_handle_undef_sparam, which(Base.convert, Tuple{Type{Tuple{Vararg{Int}}}, Tuple{}})) pop!(need_to_handle_undef_sparam, which(Base.convert, Tuple{Type{Tuple{Vararg{Int}}}, Tuple{Int8}})) + pop!(need_to_handle_undef_sparam, which(Base.__into__, Tuple{Type{Tuple{Vararg{T,N}} where {N}},Any} where {T})) @test need_to_handle_undef_sparam == Set() end end From 34d19367655e11ff245812b89943a64691100018 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 4 Jul 2020 19:38:32 -0700 Subject: [PATCH 5/7] Fix into(NTuple{1,Int}, 1:1) --- base/combinatorics.jl | 2 +- test/collections.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/base/combinatorics.jl b/base/combinatorics.jl index 9469452735da2..2a75a36ead2e7 100644 --- a/base/combinatorics.jl +++ b/base/combinatorics.jl @@ -37,7 +37,7 @@ end # Basic functions for working with permutations @inline function _foldoneto(op, acc, ::Val{N}) where N - @assert N::Integer > 0 + @assert N::Integer >= 0 if @generated quote acc_0 = acc diff --git a/test/collections.jl b/test/collections.jl index 1859f7762b2a2..8499c62c1b6fd 100644 --- a/test/collections.jl +++ b/test/collections.jl @@ -72,7 +72,7 @@ end @testset "into(Tuple, _)" begin @test @inferred(into(Tuple{}, 1:0)) === () - + @test @inferred(into(NTuple{1,Int}, [10])) === (10,) @test @inferred(into(NTuple{2,Int}, [10, 20])) === (10, 20) @test @inferred(into(NTuple{2}, [10, 20])) === (10, 20) @test @inferred(into(NTuple{2,Float64}, [10, 20])) === (10.0, 20.0) From 0b71755e3274bf2e67575ced95d2e3dc5c4ea01a Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 4 Jul 2020 19:44:32 -0700 Subject: [PATCH 6/7] Fix into(NTuple, Any[1, 2.0]) --- base/collections.jl | 8 ++++---- test/collections.jl | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/base/collections.jl b/base/collections.jl index fa64ef153b90c..81cc69263f006 100644 --- a/base/collections.jl +++ b/base/collections.jl @@ -187,10 +187,9 @@ function __into__(::Type{NTuple{N,Any}}, iterable) where {N} _too_many_items_error(N, y[1]) end -_tuple_length(::Type{<:NTuple{N,Any}}) where {N} = N - -__into__(::Type{T}, iterable) where {T <: Tuple} = - convert(T, __into__(NTuple{_tuple_length(T),Any}, iterable)) +__into__(::Type{T}, iterable) where {T<:Tuple} = convert(T, Tuple(iterable)) +__into__(::Type{T}, iterable) where {N,T<:NTuple{N,Any}} = + convert(T, __into__(NTuple{N,Any}, iterable)) function __into__(::Type{NTuple{<:Any,T}}, iterable) where {T} collection = Tuple(iterable) @@ -199,3 +198,4 @@ end __into__(::Type{NTuple{N}}, iterable) where {N} = promote(__into__(NTuple{N,Any}, iterable)...) +__into__(::Type{NTuple}, iterable) = promote(iterable...) diff --git a/test/collections.jl b/test/collections.jl index 8499c62c1b6fd..5926881e3f1b6 100644 --- a/test/collections.jl +++ b/test/collections.jl @@ -79,6 +79,8 @@ end @test into(NTuple{3}, (1, 2.0, 3im)) === (1.0 + 0.0im, 2.0 + 0.0im, 0.0 + 3.0im) @test @inferred(into(Tuple{Int,Float64}, 1:2)) === (1, 2.0) @test into(NTuple{<:Any,Float64}, 1:3) === (1.0, 2.0, 3.0) + @test into(Tuple, Any[1, 2.0]) === (1, 2.0) + @test into(NTuple, Any[1, 2.0]) === (1.0, 2.0) @test @inferred(into(NTuple{2,Int})([10, 20])) === (10, 20) @test @inferred(into(NTuple{2})([10, 20])) === (10, 20) From 43ac07d8692e3c8f773e9d1a8373909f8e131400 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sun, 5 Jul 2020 15:44:51 -0700 Subject: [PATCH 7/7] Relax `eltype` requirements Each container may choose a specific widening strategy. --- base/collections.jl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/base/collections.jl b/base/collections.jl index 81cc69263f006..6697fa7ea7670 100644 --- a/base/collections.jl +++ b/base/collections.jl @@ -9,8 +9,6 @@ Construct a new `collection` of type `T` that contains the elements in `iterable If `T` has `eltype`, `keytype`, or `valtype` information, all elements in `collection` are converted to the destination type to guarantee the constraint `collection isa T`. -Otherwise, if `IteratorEltype(collection)` is `HasEltype()` and type `T` can specify element -type in the type domain, `eltype(typeof(collection)) == eltype(typeof(iterable))` holds. If `T` has size or length information (e.g., `NTuple` and `StaticArray`), providing `collection` with unmatched size or length throws an error. @@ -110,9 +108,6 @@ overload `__into__` (resp. `__from__`) by documenting specific type bounds for ## Implementation -If `IteratorEltype(collection)` is `HasEltype()` and type `T` can specify element type in -the type domain, `eltype(typeof(collection)) == eltype(typeof(iterable))` must hold. - If `T` is a subtype of `AbstractArray`, ```julia