15
votes

How do you define a mutating function in Julia, where you want the result to be written to one of it's inputs.

I know functions exist like push!(list, a), which mutate their inputs but how can I define one of my own using the exclamation mark.

3
here is a concise example in FAQs.Gnimuc
So does that mean this could only happen when the type of variable that needs to be modified in place to be mutable itself?A.Yazdiha
@A.Yazdiha yes, that is correct. Note that even if it is mutable and you assign a new value to that variable inside your function (instead of simply mutating it by assigning by index) you'll have a new object on your hands instead.Tasos Papastylianou
@TasosPapastylianou Well, I'd say this is mostly true. See my response below for a bit of a counter point, if you're willing to use symbols and expressions. : )Michael Ohlrogge
Hahah. Nice! Well, yes, you could also mutate an object by force if you could store new values via unsafe pointer operations (e.g. unsafe_store!), but that's totally hacks to bypass intended behaviour, rather than what's supposed to happen, heheh.Tasos Papastylianou

3 Answers

25
votes

The ! is just a convention; it is not a requirement for mutating functions.

Any function can mutate its inputs. But so that it is clear that it is doing so, we suffix it with a !.

The input to be mutated does have to be mutable, though. Which excludes Strings, Tuples, Int64s, Float32s etc. As well as custom types defined without the mutable keyword.

Many types are mutable, like Vectors. But you do need to be sure to be changing their contents, rather than the references to them.

Say, for example, we want to make a function that replaces all elements of a vector with the number 2. (fill!(v,2) is the Base method to do this. But for example's sake)

what will work

Changing what v contains:

function right1_fill_with_twos!(v::Vector{Int64})
    v[:]=[2 for ii in 1:length(v)]
end

Which is the same as:

function right2_fill_with_twos!(v::Vector{Int64})
    for ii in 1:length(v)
        v[ii]=2
    end
    v 
end

So what won't work:

is changing thing what that name v points to

function wrong1_fill_with_twos!(v::Vector{Int64})
    v=[2 for ii in 1:length(v)]
end

Which is the same as:

function wrong2_fill_with_twos!(v::Vector{Int64})
    u = Vector{Int64}(length(v))
    for ii in 1:length(v)
        u[ii]=2
    end
    v = u 
end

The reason you cannot modify immutable variables (like Int64s), is because they don't have contents to modify -- they are their own contents.

This notion that you must change the Content of a variable passed in to a function, rather than change what the name is bound to (replacing the object) is a fairly standard thing in many programming languages. It comes from pass by value, where some values are references. I've heard it called the Golden Rule (of References) in Java

3
votes

It is possible to assign new values to a constant within a function if you're willing to feed that function a symbol corresponding to the constant as an argument, although I think that some might argue that this doesn't satisfy Julia programming best practices:

function modify_constant!(constant_symbol::Symbol, other_arg)
    new_val = eval(constant_symbol) + other_arg
    eval(Main, Expr(:(=), constant_symbol, new_val))
end

y = 2
modify_constant!(:y, 3)

julia> y
5

Or, if one wanted, a bit more concisely:

function modify_constant!(constant_symbol::Symbol, other_arg)
    eval(Expr(:(+=), constant_symbol, new_val))
end

For more discussion of a related issue, see this Github discussion

1
votes

For a struct type, you can use setfield!(value, name::Symbol, x) and getfield(value, name::Symbol) inside a function.

When you call the function, you need to pass the struct obj/name (value) and the field symbol (name). (x) is the new value for the struct field.