This Julia package provides macros
@modify_field!, @modify_fields! and @modify_tuple_entry!.
Macro @modify_field!
is intended
to modify a field of an object of an immutable composite type
that sits inside of a container. To illustrate
the issue, consider the following immutable structure:
immutable Immut
intfld::Int
isadded::Bool
end
Suppose a is an array of type Immut and the following
loop is needed:
# LOOP 1
t = 0
for k = 1 : n
t += a[k].intfld
a[k].isadded = true
end
Unfortunately, the above code snippet is illegal because it modifies a field of an
immutable object. [Aside: If Immut had been declared as a plain composite type
with a type declaration rather than immutable, then the above code LOOP 1 would
be fine. However, Julia programmers often put immutable composite types
into containers rather than plain composite types because the former are packed
densely in memory which may yield better performance.]
Instead, we could obtain the same effect legally via:
# LOOP 2
t = 0
for k = 1 : n
t += a[k].intfld
a[k] = Immut(a[k].intfld, true)
end
The problem with this code is that it becomes unwieldy for a composite type with
many fields. In this case, it would be hard to read and also a possible source of bugs
if the arguments to the Immut constructor were ordered incorrectly.
To use the macro in the
above example, first include the declaration using Modifyfield and then write:
# LOOP 3
t = 0
for k = 1 : n
t += a[k].intfld
@modify_field! a[k].isadded = true
end
Thus, the @modify_field! macro allows for code that mimics the clean syntax of
LOOP 1 above while
"under the hood" providing an implementation equivalent to LOOP 2 above.
The original
version of this code was by S. Vavasis and used metaprogramming and the
Val and Type types of Julia for dispatching to the
correct routine. It was greatly improved by Simon Byrne
with the incorporation of macros and generated functions.
If the user prefers to invoke a function rather than a macro, he/she can use the following statement for the same effect:
# LOOP 4
t = 0
for k = 1 : n
t += a[k].intfld
a[k] = copy_and_modify(a[k], Val{:isadded}, true)
end
Note that although the macro is intended for immutable objects in a container, it also works for immutable objects bound to a plain Julia variable:
julia> using Modifyfield.@modify_field! julia> y = Immut(6,false) Immut(6,false) julia> @modify_field! y.intfld = 9 Immut(9,false)
However, for composite types that do not occur inside of larger containers,
higher performance is
usually attained
by declaring objects such as this as
a type rather than immutable especially if
one is frequently modifying fields. (A type rather than immutable
is also stylistically preferred in this context.)
A macro is also provided for modifying multiple fields at the same time. (This is more efficient than modifying one at a time.) Here is an example of its usage:
immutable Immut2
intfld::Int
isadded::Bool
xx::Float64
end
If a is an array of Immut2 entries, then the following
loop changes the first two fields of each entry:
for k = 1 : n
@modify_fields! a[k].(intfld = k+1, isadded = true)
end
This slightly odd syntax was chosen so that field names are close to their corresponding new values to improve readability.
The parenthesized argument in the @modify_fields! macro can
name a single field, but in this case it should be followed by
a comma (so that its syntax matches the Julia tuple syntax):
@modify_fields! w.(intfld = 6,)
which is equivalent to:
@modify_field! w.intfld = 6
Similarly, the package provides a macro for modifying tuple entries. Here is an example of its execution:
julia> using Modifyfield.@modify_tuple_entry! julia> t = (5,9.5,true) (5,9.5,true) julia> @modify_tuple_entry! t[2] = false (5,false,true)
There is also an equivalent functional call:
julia> using Modifyfield.copy_and_modify_tup
julia> t = (5,9.5,true)
(5,9.5,true)
julia> t = copy_and_modify_tup(t, Val{2}, true)
(5,true,true)
As in the case of immutables, the implementation of
@modify_tuple_entry! actually copies the entire tuple over.
A couple of cautionary notes are in order. First, the macro
@modify_tuple_entry!
requires a literal integer for the subscript
(which is 2 in the above example) of the tuple.
A variable or more general expression may not be
used.
The function-call version copy_and_modify_tup can take a variable
subscript, e.g., Val{j} as its second argument, but this leads
to a loss of performance because the compiler cannot fully
determine argument types, and therefore the method dispatch happens at
run time.
Second, the main purpose of this macro is for tuples that
are packed inside of some other container in a high-performance
setting. If one is modifying bare tuples such as t in the above
example, then in most cases a cell array (Array{Any,1}) would be
preferable to a tuple.