Description
tl;dr Can we have an API (say) into(T::Type, iterable) -> collection::T
for creating a collection
of type T
from an iterable
?
Problem
Given an arbitrary iterable and arbitrary container type, there is no consistent API to construct the container. The closest thing probably is to call the constructor. Indeed, that's what the documentation recommends
if
T
is a mutable collection type thenT(x)
should always make a new collection (copying elements fromx
).--- https://docs.julialang.org/en/v1/manual/conversion-and-promotion/#Mutable-collections-1
However, even things like Vector(1 + x for x in 1:10)
or Vector(Dict(:a => 1))
do not work (ref #16029). For Vector
, we can use collect
but having arbitrary factory function for each custom container type is not great.
Furthermore, this recommendation is not always reasonable. For example, if you want to implement own vec
with
struct VectorView{T,P <: AbstractArray{T}} <: AbstractVector{T}
parent::P
end
you'd want to VectorView(array)
to not copy the input array
. It is reasonable to expect constructors to do only minimum amount of work, especially for "wrapper" types. For example, StructArray(a = vector)
doesn't copy the input vector
(although it's not of the form T(x)
).
Another problem is that constructors usually have a particular semantics. For example:
- It may be desirable that
T(x)
whereT :< StaticArray
to treatx
as an element Inconsistencies with multi-argument constructors JuliaArrays/StaticArrays.jl#518, Fix method amguity in Scalar(::StaticArray) JuliaArrays/StaticArrays.jl#774. - "Container semantics" of the constructor (copy if mutable) does not apply to various wrapper matrices in LinearAlgebra (e.g.,
Adjoint
,Diagonal
,Hermitian
, ...); i.e., it is desirable if the data is not copied at all when calling these constructors. - It is desirable to use the default constructor so that Setfield.jl gives us a composable API for manipulating nested immutables easily. See also ConstructionBase.jl documentation on Tips for designing types (probably opinionated view of mine).
All of these points suggest that it would be nice to have an entry point for constructing a container given its type and the input iterable, separated from the constructor.
For example, StaticArrays has sacollect(::Type{<:StaticArray}, itr)
JuliaArrays/StaticArrays.jl#792 for collecting an iterable as a given type. It'd be great to have a uniform interface so that it is possible to overload.
Proposal
API
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 convert
ed to the destination type. Otherwise, if IteratorEltype(collection)
is HasEltype()
and type T
can specify element type in the type domain, eltype(typeof(new)) == eltype(typeof(collection))
holds.
If T
has size or length information (e.g., StaticArray
), providing collection
with unmatched size or length throws an error.
Default implementation
It is probably OK for many cases if we use the fallback implementation
into(T::Type, collection) = T(collection)
Alternatively, a safer implementation is:
into(T::Type, collection) = T(collect(collection))
Naming bikeshedding
The name into
is something I stole from Clojure https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/into
In Python, I see $Class.from_$thing(thing)
classmethod pattern. Translating this to Julia, from(T, iterable)
can also be a reasonable choice?
Some more random alternatives:
collect_to(T, iterable)
collectto(T, iterable)
copyto(T, iterable)
copyas(T, iterable)
makeof(T, iterable)
pour(T, iterable)
funnel(T, iterable)
Related discussions
-
Merge collect() into Vector()? #16029: Merge collect() into Vector()
This would solve the problem for
Vector(iterable)
,Array(iterable)
, etc. However, we still need a dedicated generic factory function for the reasons listed above. -
design of array constructors #24595: design of array constructors
Most of the problems is solved for Julia 1.x?
-
Generic abstract array construction without instances #25107: Generic abstract array construction without instances
Since this is a discussion for common interface for creating uninitialized arrays, it's somewhat orthogonal to this issue. But
into(T, iterable)
withiterable::UndefArray
can be used for this.