ConstructionBase.constructorofFunction
constructorof(T::Type) -> constructor

Return an object constructor that can be used to construct objects of type T from their field values. Typically constructor will be the type T with all parameters removed:

julia> using ConstructionBase

julia> struct T{A,B}
           a::A
           b::B
       end

julia> constructorof(T{Int,Int})
T

It is however not guaranteed, that constructor is a type at all:

julia> struct S
           a
           b
           checksum
           S(a,b) = new(a,b,a+b)
       end

julia> ConstructionBase.constructorof(::Type{<:S}) =
           (a, b, checksum=a+b) -> (@assert a+b == checksum; S(a,b))

julia> constructorof(S)(1,2)
S(1, 2, 3)

julia> constructorof(S)(1,2,4)
ERROR: AssertionError: a + b == checksum

Instead constructor can be any object that satisfies the following properties:

  • It must be possible to reconstruct an object from the elements of getfields:
ctor = constructorof(typeof(obj))
@assert obj == ctor(getfields(obj)...)
@assert typeof(obj) == typeof(ctor(getfields(obj)...))
  • The other direction should hold for as many values of args as possible:
ctor = constructorof(T)
getfields(ctor(args...)) == args

For instance given a suitable parametric type it should be possible to change the type of its fields:

julia> struct T{A,B}
           a::A
           b::B
       end

julia> t = T(1,2)
T{Int64, Int64}(1, 2)

julia> constructorof(typeof(t))(1.0, 2)
T{Float64, Int64}(1.0, 2)

julia> constructorof(typeof(t))(10, 2)
T{Int64, Int64}(10, 2)

constructorof belongs to the raw level. constructorof is generated for all anonymous Functions lacking constructors, identified as having gensym # in their names. A custom struct <: Function with a gensym name may need to define constructorof manually.

See also Tips section in the manual

ConstructionBase.getfieldsFunction
getfields(obj) -> NamedTuple
getfields(obj::Tuple) -> Tuple

Return a NamedTuple containing the fields of obj. On Tuples getfields is the identity function instead, since Tuple fields have no symbolic names.

Examples

julia> using ConstructionBase

julia> struct S{A,B}
           a::A
           b::B
       end

julia> getfields(S(1,2))
(a = 1, b = 2)

julia> getfields((a=10,b=20))
(a = 10, b = 20)

julia> getfields((4,5,6))
(4, 5, 6)

Specification

getfields belongs to the the raw level. Semantically getfields boils down to getfield and fieldnames:

function getfields(obj::T) where {T}
    fnames = fieldnames(T)
    NamedTuple{fnames}(getfield.(Ref(obj), fnames))
end

However the actual implementation can be more optimized. For builtin types, there can also be deviations from this semantics:

  • getfields(::Tuple)::Tuple since Tuples don't have symbolic fieldnames
  • There are some types in Base that have undef fields. Since accessing these results in an error, getfields instead just omits these.

Implementation

The semantics of getfields should not be changed for user defined types. It should return the raw fields as a NamedTuple in the struct order. In other words it should be equivalent to

function getfields(obj::T) where {T}
    fnames = fieldnames(T)
    NamedTuple{fnames}(getfield.(Ref(obj), fnames))
end

even if that includes private fields of obj. If a change of semantics is desired, consider overloading getproperties instead. See also getproperties, constructorof

ConstructionBase.getpropertiesFunction
getproperties(obj)::NamedTuple
getproperties(obj::Tuple)::Tuple

Return the properties of obj as a NamedTuple. Since Tuple don't have symbolic properties, getproperties is the identity function on tuples.

Examples

julia> using ConstructionBase

julia> struct S
           a
           b
           c
       end

julia> s = S(1, 2, 3)
S(1, 2, 3)

julia> getproperties(s)
(a = 1, b = 2, c = 3)

julia> getproperties((10,20))
(10, 20)

Specification

getproperties belongs to the semantic level. getproperties guarantees a couple of invariants. When overloading it, the user is responsible for ensuring them:

  1. getproperties should be consistent with Base.propertynames, Base.getproperty, Base.setproperty!. Semantically it should be equivalent to: julia function getproperties(obj) fnames = propertynames(obj) NamedTuple{fnames}(getproperty.(Ref(obj), fnames)) end
  2. getproperties is defined in relation to setproperties so that:
    obj == setproperties(obj, getproperties(obj))
    The only exception from this semantics is that undefined properties may be avoided in the return value of getproperties.

Implementation

getproperties is defined by default for all objects. It should be very rare that a custom type MyType, has to implement getproperties(obj::MyType). Reasons to do so are undefined fields or performance considerations.

ConstructionBase.setpropertiesFunction
setproperties(obj, patch::NamedTuple)

Return a copy of obj with properties updated according to patch.

Examples

julia> using ConstructionBase

julia> struct S
           a
           b
           c
       end

julia> s = S(1,2,3)
S(1, 2, 3)

julia> setproperties(s, (a=10,c=4))
S(10, 2, 4)

julia> setproperties((a=1,c=2,b=3), (a=10,c=4))
(a = 10, c = 4, b = 3)

There is also a convenience method, which builds the patch argument from keywords:

setproperties(obj; kw...)

Examples

julia> using ConstructionBase

julia> struct S
           a
           b
           c
       end

julia> o = S(10, 2, 4)
S(10, 2, 4)

julia> setproperties(o, a="A", c="cc")
S("A", 2, "cc")

Specification

setproperties belongs to the semantic level. If satisfies the following invariants:

  1. Purity: setproperties is supposed to have no side effects. In particular setproperties(obj, patch::NamedTuple) may not mutate obj.
  2. Relation to propertynames and fieldnames: setproperties relates to propertynames and getproperty, not to fieldnames and getfield. This means that any subset p₁, p₂, ..., pₙ of propertynames(obj) is a valid set of properties, with respect to which the lens laws below must hold.
  3. setproperties is defined in relation to getproperties so that:
    obj == setproperties(obj, getproperties(obj))
  4. setproperties should satisfy the lens laws:

For any valid set of properties p₁, p₂, ..., pₙ, following equalities must hold:

  • You get what you set.
let obj′ = setproperties(obj, ($p₁=v₁, $p₂=v₂, ..., $pₙ=vₙ))
    @assert obj′.$p₁ == v₁
    @assert obj′.$p₂ == v₂
    ...
    @assert obj′.$pₙ == vₙ
end
  • Setting what was already there changes nothing:
@assert setproperties(obj, ($p₁=obj.$p₁, $p₂=obj.$p₂, ..., $pₙ=obj.$pₙ)) == obj
  • The last set wins:
let obj′ = setproperties(obj, ($p₁=v₁, $p₂=v₂, ..., $pₙ=vₙ)),
    obj′′ = setproperties(obj′, ($p₁=w₁, $p₂=w₂, ..., $pₙ=wₙ))
    @assert obj′′.$p₁ == w₁
    @assert obj′′.$p₂ == w₂
    ...
    @assert obj′′.$pₙ == wₙ
end

Implementation

For a custom type MyType, a method setproperties(obj::MyType, patch::NamedTuple) may be defined. When doing so it is important to ensure compliance with the specification.

  • Prefer to overload constructorof whenever makes sense (e.g., no getproperty method is defined). Default setproperties is defined in terms of constructorof and getproperties.

  • If getproperty is customized, it may be a good idea to define setproperties.

Warning

The signature setproperties(obj::MyType; kw...) should never be overloaded. Instead setproperties(obj::MyType, patch::NamedTuple) should be overloaded.