Skip to content

RFC: into(T::Type, iterable) -> collection::T #36288

Open
@tkf

Description

@tkf

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 then T(x) should always make a new collection (copying elements from x).

--- 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:

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 converted 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    collectionsData structures holding multiple items, e.g. setsdesignDesign of APIs or of the language itselffeatureIndicates new feature / enhancement requests

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions