Materialize transducer into existing storage

I defined a reducible for my Kernel type, which works fine, and I can materialize pipelines with collect, etc.

I now want to be able to write a function that does kernel |> Map(func) |> # ... materialize into existing vector storage.

Basically I want to do what Base.map! does to eagerly construct a collection from a transducer pipeline, but storing the result in a destination collection without an intermediary allocation. I see there is a map! in Transducers.jl, but it seems to want 3 arguments, and being new to Transducers.jl, I really have no idea how to work with that, or if it’s even what I’m looking for.

Thanks for any help!

Is your Kernel <: AbstractArray? If so, map!(Map(func), dest, kernel) should work fine, no? Otherwise, sizehint!ing an empty dest array and using append!(Map(func), dest, kernel) or append!!(dest, kernel |> Map(func)) should work, I think.

julia> map!(Map(identity), dest, kernel)

The above hangs my REPL forever. No stack overflow, just no return value with heavy CPU usage for something that normally takes nanoseconds.

Edit: Aborting the evaluation I get:

ERROR: InterruptException:
Stacktrace:
 [1] map!(f::Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{M
ap{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map
{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{M
ap{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map
{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{M
ap{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map
{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{M
ap{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map
{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{M
ap{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map
{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{M
ap{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map
{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{M
ap{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map
{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{M
ap{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map
{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{M
ap{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map
{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{M
ap{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map
{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{M
ap{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map
{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{M
ap{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map
{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{M
ap{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map
{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{M
ap{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map
{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{M
ap{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map
{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{M
ap{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map
{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{Map{typeof(identity)}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}, dest::Vector{Cell}, kernel::Kernel
{Rect}) (repeats 890 times)
   @ DungeonGenerator ~/projects/Julia/DungeonGenerator/src/DungeonGenerator.jl:80
 [2] top-level scope
   @ REPL[15]:1

The latter does work, and I was able to do this on my own previously, but obviously, instead of re-using existing storage, it grows the vector. Sure I could sizehint! it to prevent allocation, but the goal is to just do a memory copy and retain the length of the destination vector, or at least start writing from the beginning rather than the end.

Now that I read that again: Shouldn’t it just be map!(func, dest, kernel)?

Side note: I have never used Transducers before, so it’s unlikely anything useful is going to come out of me blindly suggesting things without access to the original codebase :smiley:

No that won’t do, as a transducer is not a function. I finally figured something out though that is type-stable, doesn’t allocate, and is extremely fast (thanks to @jakobnissen for the starting point):

function Base.map!(f::F, dest, kernel::Kernel) where {F}
    foldl(kernel |> Map(f) |> Enumerate(); init=dest) do v, (i, e)
        i > length(dest) && resize!(dest, i)
        v[i] = e
        v
    end
end

If anyone sees any room for improvement, I’d like to know, being relatively new to Julia, and extremely new to Transducers.jl myself. Thank you!