# New package: Collects.jl - meant to improve upon and generalize the interface of collect

**URL:** <https://discourse.julialang.org/t/new-package-collects-jl-meant-to-improve-upon-and-generalize-the-interface-of-collect/129468>\
**Category:** Package Announcements\
**Tags:** package, announcement, collection, iterators, collections\
**Created:** [May 30, 2025, 10:32am UTC](https://discourse.julialang.org/t/new-package-collects-jl-meant-to-improve-upon-and-generalize-the-interface-of-collect/129468 "2025-05-30T10:32:20Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [May 30, 2025, 10:32am UTC](https://discourse.julialang.org/t/new-package-collects-jl-meant-to-improve-upon-and-generalize-the-interface-of-collect/129468/1 "2025-05-30T10:32:20Z")

</div>

Just requested the registration of the new package, Collects.jl, meaning it should be registered after three days pass.

Basically it’s meant to replace `collect`, especially when the desired output container type is not `Array`, as the new package’s interface takes the container type as a method argument. Another generalization with respect to `collect` is that the user now has control over how empty iterators are handled. In particular, type inference is not used by default, which is friendlier to the compiler optimizer, and to the correctness of the package ecosystem. The default behavior is to throw when the element type can’t be deduced from either `eltype`, the elements of the iterator, or the output type argument.

The main exported names are `Collect` and `collect_as`. The former is meant for package authors implementing the Collects.jl interface, while the latter should be more convenient for users.

The `collect_as` function behaves similarly to `collect`, except that the first argument is the container type instead of the element type. Apart from two positional arguments, `collect_as` also takes an optional keyword argument, controlling how the element type of empty iterators is handled. See the Readme for more information:

> **[GitHub - JuliaCollections/Collects.jl: Collect the elements of a given collection into a...](https://github.com/JuliaCollections/Collects.jl)**
>
> Collect the elements of a given collection into a collection of the given type. Generalizes \`collect\`! Implement for types you own!

Some notes:

- Collects.jl implements its interface for (some) collection types from `Base`, otherwise it’s an interface package, meaning that its interface is meant to be implemented in other packages for types that package owns.

- Unlike `collect`, Collects.jl does not use `Base.IteratorEltype`. Example where `Base.IteratorEltype` matters for `collect`:

---

<div class="post-metadata">

**Author:** ![laborg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/laborg/32/5474_2.png) [@laborg](https://discourse.julialang.org/u/laborg)\
**Post date:** [June 1, 2025, 5:20am UTC](https://discourse.julialang.org/t/new-package-collects-jl-meant-to-improve-upon-and-generalize-the-interface-of-collect/129468/2 "2025-06-01T05:20:42Z")

</div>

I know this is going to be against the stream of “everything should be externalized”, but it feels like a good function to be in located in Base. I’m quite sure I would use this function if it was in Base, but am equally sure that I will not add Collects.jl as a requirement to any of my packages. There is just too much overhead for a single and simple function.

---

<div class="post-metadata">

**Author:** ![CameronBieganek](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cameronbieganek/32/6915_2.png) [@CameronBieganek](https://discourse.julialang.org/u/CameronBieganek)\
**Post date:** [June 1, 2025, 4:22pm UTC](https://discourse.julialang.org/t/new-package-collects-jl-meant-to-improve-upon-and-generalize-the-interface-of-collect/129468/3 "2025-06-01T16:22:11Z")

</div>

Sorry for the last minute bikeshedding, but I think `fromiter` would be a good name for this. It reads a little more naturally with the argument order, i.e. `fromiter(Set, x)` is like

```julia
Set `fromiter` x

```

which is similar to how `contains(haystack, needle)` can be read as

```julia
haystack `contains` needle

```

and `occursin(needle, haystack)` can be read as

```julia
needle `occursin` haystack

```

There is also some precedence for `fromiter` or `from_iter` in other languages, e.g.,

> **[FromIterator in std::iter - Rust](https://doc.rust-lang.org/std/iter/trait.FromIterator.html)**
>
> Conversion from an \`Iterator\`.

> **[numpy.fromiter — NumPy v2.2 Manual](https://numpy.org/doc/2.2/reference/generated/numpy.fromiter.html)**

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [June 2, 2025, 10:28am UTC](https://discourse.julialang.org/t/new-package-collects-jl-meant-to-improve-upon-and-generalize-the-interface-of-collect/129468/4 "2025-06-02T10:28:16Z")

</div>

> [@CameronBieganek](#):
>
> Sorry for the last minute bikeshedding, but I think `fromiter` would be a good name for this.

Opening a poll for voting on alternative names, but I’m not willing to give up `collect` in the name, unless there’s a _really_ good alternative. EDIT: uhh the package just got registered. I’ll make a v2 ASAP if necessary.

> [@CameronBieganek](#):
>
> There is also some precedence for `fromiter` or `from_iter` in other

Sure, but there’s even more precedent for `collect`, I think.

## Poll

_Poll ([view on site](https://discourse.julialang.org/t/new-package-collects-jl-meant-to-improve-upon-and-generalize-the-interface-of-collect/129468/4))_

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [June 2, 2025, 10:35am UTC](https://discourse.julialang.org/t/new-package-collects-jl-meant-to-improve-upon-and-generalize-the-interface-of-collect/129468/5 "2025-06-02T10:35:52Z")

</div>

> [@laborg](#):
>
> it feels like a good function to be in located in Base

`collect` already _is_ in `Base`, though. If a PR is going to be made against JuliaLang/julia to add a replacement for `collect`, the replacement better be _really_ good, otherwise there’s going to be yet another replacament added to `Base` two years later. And the only way to be sure is to test it out in the package ecosystem.

> [@laborg](#):
>
> a single and simple function

I’m sure you’re underestimating the choices that went into both the design of the interface and into the implementation.

Case in point, even though `collect_as` existed for some time as a function in FixedSizeArrays.jl, after creating this standalone package, and even after starting the registration process, there’s been significant changes.

Furthermore, what you’re saying doesn’t really make sense: this is an interface package, meant to have its interface implemented by an arbitrary number of other packages. So you can’t just reimplement it or vendor it locally.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [June 3, 2025, 10:48pm UTC](https://discourse.julialang.org/t/new-package-collects-jl-meant-to-improve-upon-and-generalize-the-interface-of-collect/129468/6 "2025-06-03T22:48:00Z")

</div>

Regarding the poll, is there someone able to see the results? I just get this meaningless attempt at a visualization:

 ![poll](https://global.discourse-cdn.com/julialang/original/3X/2/2/22c1d8c24ad297bf00a9d9bfd7aecd8c12f4371e.png)

The poll should be configured so that each vote is public, but I don’t understand if that is actually available.

---

<div class="post-metadata">

**Author:** ![digital\_carver](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/digital_carver/32/33818_2.png) [@digital\_carver](https://discourse.julialang.org/u/digital_carver)\
**Post date:** [June 3, 2025, 11:31pm UTC](https://discourse.julialang.org/t/new-package-collects-jl-meant-to-improve-upon-and-generalize-the-interface-of-collect/129468/7 "2025-06-03T23:31:45Z")

</div>

Seems like the ranked choice voting is [supposed](https://meta.discourse.org/t/creating-and-managing-polls/77548/117) to go in rounds and in the end have quite different visuals: the [third image in this original PR](https://github.com/discourse/discourse/pull/27155) and a bunch of images in [this thread](https://meta.discourse.org/t/ranked-choice-poll-does-not-reflect-change-of-votes-in-outcome/321227/4) show it. Not sure why this one displays the results in this useless way. Perhaps worth creating a thread in that meta.discourse website after getting info about the Discourse version being used here from the admins/mods.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [February 16, 2026, 12:16am UTC](https://discourse.julialang.org/t/new-package-collects-jl-meant-to-improve-upon-and-generalize-the-interface-of-collect/129468/8 "2026-02-16T00:16:20Z")

</div>

V1.1 is released as of recently, coming with a new feature, requested by @PatrickHaecker: `collect_as(output_type)` now returns something akin to `Base.Fix1(collect_as, output_type)` instead of throwing `MethodError`.

There has also been lots of performance and inference improvements. Some day maybe I create a nice benchmark suite with lots of different classes of iterators, comparing `collect_as` against `collect`.

Also planning on expanding the interface with the ability for the caller to specify a size hint. This might be helpful for the iterator types which do not support `length`, in case the caller has an idea for the minimum length. On the issue tracker: [allow caller to provide size hint · Issue #55 · JuliaCollections/Collects.jl · GitHub](https://github.com/JuliaCollections/Collects.jl/issues/55)

Also, there is now a docs page, via Github Pages and Documenter.jl.

---

<div class="post-metadata">

**Author:** ![PatrickHaecker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/patrickhaecker/32/222891_2.png) [@PatrickHaecker](https://discourse.julialang.org/u/PatrickHaecker)\
**Post date:** [February 16, 2026, 4:55am UTC](https://discourse.julialang.org/t/new-package-collects-jl-meant-to-improve-upon-and-generalize-the-interface-of-collect/129468/9 "2026-02-16T04:55:52Z")

</div>

Thanks, @nsajko, for this addition. Although `Array`s are great, Julia has so many more great collections and Collects.jl makes it even easier to use and mix them.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [February 17, 2026, 11:36pm UTC](https://discourse.julialang.org/t/new-package-collects-jl-meant-to-improve-upon-and-generalize-the-interface-of-collect/129468/10 "2026-02-17T23:36:31Z")

</div>

> [@nsajko](#):
>
> Some day maybe I create a nice benchmark suite with lots of different classes of iterators, comparing `collect_as` against `collect`.

OK, so here is some measurements.

Here are the different configurations varied in the experiment:

- `collect` vs `collect_as`

- With element type specified (`collect(elt, iterator)` or `collect_as(Array{elt}, iterator)`) vs not specified (`collect(iterator)` or `collect_as(Array, iterator)`).

- `eltype(iterator) === Any` vs `eltype(iterator) === Float32`.

- `Base.IteratorSize(iterator) === Base.HasLength()` vs `Base.IteratorSize(iterator) === Base.SizeUnknown()`

- With iterator length ranging from 1 to 600.

In total: 2^4 \cdot 600 benchmarks.

In all cases a call of `iterate` is cheap and elements of the iterator are `Float32` numbers ranging from 1 to the length.

Relevant software versions:

- Collects.jl v1.1.0

- BenchmarkTools.jl v1.6.3

- Julia 1.14.0-DEV.1734 (commit 4acab830b60)

Here’s a first attempt at a visualization:

 ![vega-lite-defaulttheme](https://global.discourse-cdn.com/julialang/original/3X/a/9/a9d69ac44a2c1dde2ac7edb15197ef0c6c8e1be0.svg)

So it seems like performance is mostly the same between `collect` and `collect_as`, except in the case of iterators which have an imprecise `eltype` and, simultaneously, do not implement `length` (`Base.SizeUnknown()`). Even in the latter case there is no clear winner.

This is the Julia code for the benchmarks:

> **bench.jl**
>
> ```julia
> module IteratorExample
> export iterator_example
> function identity_function(x) # behaves the same as `identity`, but a new function, to avoid special code paths meant for `typeof(identity)`
> x
> end
> function returns_true_function(::Any) # behaves the same as `Returns(true)`, but a new function, to avoid special code paths meant for `Returns`
> true
> end
> const iterator_with_obscured_eltype = Base.Fix1(Iterators.map, identity_function)
> const iterator_with_obscured_iteratorsize = Base.Fix1(Iterators.filter, returns_true_function)
> function iterator_with_length_start_step(; length, start, step)
> it = Iterators.countfrom(start, step)
> Iterators.take(it, length)
> end
> function branch((f, g), x, b::Bool)
> if b
> f(x)
> else
> g(x)
> end
> end
> function iterator_example(; length, start, step = true, precise_eltype::Bool, precise_iteratorsize::Bool)
> it = iterator_with_length_start_step(; length, start, step)
> it = branch((identity, iterator_with_obscured_eltype), it, precise_eltype)
> it = branch((identity, iterator_with_obscured_iteratorsize), it, precise_iteratorsize)
> it
> end
> end
> 
> const samples = parse(Int, ARGS[1])
> const evals = parse(Int, ARGS[2])
> const min_n = parse(Int, ARGS[3])
> const max_n = parse(Int, ARGS[4])
> 
> print(
> stderr,
> "samples = $samples\n",
> "evals = $evals\n",
> "min_n = $min_n\n",
> "max_n = $max_n\n",
> )
> 
> using .IteratorExample
> using Collects: collect_as
> using BenchmarkTools: @benchmarkable
> 
> const IteratorId = Tuple{
> Bool, # `eltype` is precise
> Bool, # `IteratorSize` is precise
> Int, # `length`
> }
> 
> function iterator_ids(lengths)
> bools = (false, true)
> Iterators.product(bools, bools, lengths)
> end
> 
> function measure_performance(
> (name, func)::Tuple{String, Any},
> (precise_eltype, precise_iteratorsize, iterator_length)::IteratorId,
> )
> iterator = iterator_example(;
> length = iterator_length,
> start = 1f0,
> precise_eltype,
> precise_iteratorsize,
> )
> GC.gc()
> GC.gc()
> GC.enable(false)
> bench = @benchmarkable ($func)($iterator) evals=evals samples=samples seconds=Inf gctrial=false
> trial = run(bench)
> GC.enable(true)
> GC.gc()
> GC.gc()
> estimate = minimum(trial)
> iterator_id = (
> precise_eltype,
> precise_iteratorsize,
> iterator_length,
> )
> measurement = (;
> minimum_time = estimate.time,
> allocated_byte_count = estimate.memory,
> allocation_count = estimate.allocs,
> )
> (name, iterator_id, measurement)
> end
> 
> function show_results_as_json_table_row(
> io::IO,
> name::String,
> (prec_e, prec_i, n)::IteratorId,
> (; minimum_time, allocated_byte_count, allocation_count),
> )
> pr = Base.Fix1(print, io)
> f = Base.Fix1(pr, ", ")
> eltype_iterator = if prec_e
> "Float32"
> else
> "Any"
> end
> IteratorSize_iterator = if prec_i
> "HasLength()"
> else
> "SizeUnknown()"
> end
> pr(" { ")
> pr(lazy"\"BenchmarkTools.jl parameter `evals`\": $evals")
> f()
> pr(lazy"\"BenchmarkTools.jl parameter `samples`\": $samples")
> f()
> pr(lazy"\"description\": \"$name\"")
> f()
> pr(lazy"\"length(iterator)\": $n")
> f()
> pr(lazy"\"eltype(iterator)\": \"$eltype_iterator\"")
> f()
> pr(lazy"\"IteratorSize(iterator)\": \"$IteratorSize_iterator\"")
> f()
> pr(lazy"\"minimal measured time (ns)\": $minimum_time")
> f()
> pr(lazy"\"measured allocated bytes\": $allocated_byte_count")
> f()
> pr(lazy"\"measured allocations\": $allocation_count")
> pr(' ')
> pr("} ")
> nothing
> end
> 
> function todo_how_to_name_this_function(f::F, arg) where {F}
> f()
> arg
> end
> 
> function measure_and_report_as_json(io::IO)
> pr = Base.Fix1(print, io)
> f = splat(Base.Fix1(show_results_as_json_table_row, io)) ∘ measure_performance
> g = Base.Fix1(todo_how_to_name_this_function, Base.Fix1(pr, ",\n"))
> collects = (
> ("collect(x)", collect),
> ("collect(Float32, x)", (x -> collect(Float32, x))),
> ("collect_as(Array, x)", collect_as(Array)),
> ("collect_as(Array{Float32}, x)", collect_as(Array{Float32})),
> )
> it = Iterators.product(collects, iterator_ids(min_n:max_n))
> (fir, res) = Iterators.peel(it)
> pr("[\n")
> f(fir...)
> foreach(splat(f) ∘ g, res)
> pr("\n]\n")
> end
> 
> measure_and_report_as_json(stdout)
> 
> ```

Linked is the Vega Lite JSON, including the data inline:

> <https://gist.github.com/nsajko/349d3145018389650bd0b06151af298e>

EDIT: here is a visualization for iterator lengths not greater than 50:

 ![50](https://global.discourse-cdn.com/julialang/original/3X/a/2/a2d890543522f449eb5135285904b580360fd47d.svg)
