Why there is no in-place `map!` on arrays?

I am wondering why we could not have a in-place map! so that the destination and the source are the same:

julia> a = [1, 2, 3];

julia> map!(-, a)
3-element Vector{Int64}:
 -1
 -2
 -3

I am using this pattern a lot and really hope we can have one. I suppose I am not the only one. What might block this design?

1 Like

You can technically just do map!(-,a,a) which does work. But might have issues with aliasing.

But might have issues with aliasing.

Could you please elaborate a little on what “issues with aliasing” means? I sometimes have a long variable name aVeryLongVariableName and it is ugly to write:

map!(f, aVeryLongVariableName, aVeryLongVariableName)
1 Like

I believe that you might be free to define map!(op, a) = map!(op, a, a)?

I think type stability is the issue—what if you have an array of floats but f returns a string?

For - you can use a[:] *= -1.

2 Likes

That is what I am doing now. But if I want to use it a lot, it seems overkilling to write a package just about map!. And I am not sure if it is type piracy.

For - you can use a[:] *= -1 .

This is just an example, I usually have more complicated functions to map on an array.

Possibly. Call it map1! then?

Tamas_Papp suggested mapinto!. I think it is better.

2 Likes

I think type stability is the issue—what if you have an array of floats but f returns a string?

I think map! already checks that?

julia> a = [1, 2, 3];

julia> map!(sin, a, a)
ERROR: InexactError: Int64(0.8414709848078965)
Stacktrace:
 [1] Int64
   @ ./float.jl:788 [inlined]
 [2] convert
   @ ./number.jl:7 [inlined]
 [3] setindex!
   @ ./array.jl:966 [inlined]
 [4] map!(f::typeof(sin), dest::Vector{Int64}, A::Vector{Int64})
   @ Base ./abstractarray.jl:2927
 [5] top-level scope
   @ REPL[2]:1

julia> map!(string, a, a)
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int64
Closest candidates are:
  convert(::Type{T}, ::Ptr) where T<:Integer at pointer.jl:23
  convert(::Type{T}, ::T) where T<:Number at number.jl:6
  convert(::Type{T}, ::Number) where T<:Number at number.jl:7
  ...
Stacktrace:
 [1] setindex!(A::Vector{Int64}, x::String, i1::Int64)
   @ Base ./array.jl:966
 [2] map!(f::typeof(string), dest::Vector{Int64}, A::Vector{Int64})
   @ Base ./abstractarray.jl:2927
 [3] top-level scope
   @ REPL[3]:1

So I just do mapinto!(f, arg) = map!(f, arg, arg).

1 Like

I think this is referring to `broadcast!(f, x, x)` slower, prevents SIMD? · Issue #43153 · JuliaLang/julia · GitHub, but according to `map!` does not check for aliasing · Issue #46352 · JuliaLang/julia · GitHub that shouldn’t be an issue for map!.

let tmp = aVeryLongVariableName
   map!(f, tmp, tmp)
end
2 Likes
julia> a = collect(1:10)

julia> map(-, view(a, 7:-1:3))
5-element Vector{Int64}:
 -7
 -6
 -5
 -4
 -3

julia> map!(-, view(a, 1:5), view(a, 7:-1:3));

julia> a
10-element Vector{Int64}:
 -7
 -6
 -5
 -4
  5
  6
  7
  8
  9
 10

something like this; the overlapping cause incorrect result but we don’t have alias checking so map!() didn’t error

4 Likes

This creates two unnecessary temporary arrays, and traverses a three times instead of once. Instead use a .*= -1.

3 Likes

See Proposal to use map!(f,A) for in place modification of collections · Issue #31677 · JuliaLang/julia · GitHub.

3 Likes