Thank you, I appreciate your feedback!

Yes, Transducers.jl’s `collect`

uses inference API. Also, there are many other places (e.g., transducers like `Partition`

which needs an internal buffer) that relies on type inference.

Great question! I’ve been pondering about how to avoid relying on the type inference. Note that, especially in transducers, this problem also applies when the output is not a container. For example, consider `mapfoldl(Filter(isodd) |> Map(x -> x / 2), +, 1:10)`

. It requires to choose a “good” initial zero unless you provide it with `init`

keyword argument. Furthermore, the first resulting (mapped) item cannot be used as the initial guess since the first item may be filtered out. The problem you mention corresponds to the case you use `push!`

instead of `+`

(roughly speaking).

The direction I want to explore to solve it is to define “generic” left-identity type like

```
struct Identity end
Base.*(::Identity, x) = x
Base.+(::Identity, x) = x
# and so on...
```

and then use `Identity()`

as the default `init`

. (I am actually thinking to use `struct Identity{OP} end`

with `Base.*(::Identity{*}, x) = x`

to avoid confusion/bug due to miss-use of an identity for one reducing function with another reducing function. Also, above implementation like `*(::Identity, x)`

can probably be implemented as a transducer.) It would require each `foldl`

implementation to embrace widen-as-needed approach. This is because the accumulation variable can change type from `Identity`

to, e.g., `Float64`

or `Vector{Int}`

at any point. Also, this change of type can occur multiple times (e.g., `Identity`

→ `Vector{Int}`

→ `Vector{Union{Missing,Int}}`

). It means that you need to write a somewhat non-trivial `foldl`

for each container type (or any “reducible”). Although it is a downside in some sense, the upside is that you only need to write it once (well, if this plan works…).

Once this infrastructure is in place, I think widening-based `collect`

can be implemented by just using

```
push_or_widen!(::Identity, x) = [x]
push_or_widen!(xs::Vector{T}, x::S) where {T, S <: T} = push!(xs, x)
push_or_widen!(xs::Vector, x) = push!(append!(Union{eltype(xs), typeof(x)}[], xs), x)
```

instead of `push!`

. (Actually, you can already feed this function to `mapfoldl`

. The only problem is that it is not going to be type-stable.)

I think Julia handles it nicely since it is a dynamic language. That is to say, the worst case scenario is the dynamic dispatch which should still result in a logically correct answer. One of the examples is the prime sieve. This is just a for-fun demo (don’t use it if you need speed!) but it does show that Julia can handle “type explosion.”

But I don’t know if it is possible to optimize such use-case unless the input container is a small container like tuples that can be completely unrolled.

For “sink” containers, I think it would be relatively easy as I think you only need to implement two methods:

```
push_or_widen!(xs::MyContainer{T}, x::S) where {T, S <: T} = push!(xs, x)
push_or_widen!(xs::MyContainer, x) = push!(append!(MyContainer{Union{eltype(xs), typeof(x)}}(), xs), x)
```

All the hard part would be in the `foldl`

implementation for the “source” reducible container (as I mentioned in the answer to the first question).