|
1 | 1 | module ConstructionBase
|
2 | 2 |
|
3 |
| -greet() = print("Hello World!") |
| 3 | +export setproperties |
| 4 | +export constructorof |
| 5 | + |
| 6 | + |
| 7 | +""" |
| 8 | + constructorof(T::Type) |
| 9 | +
|
| 10 | +Return an object `ctor` that can be used to construct objects of type `T` |
| 11 | +from their field values. Typically `ctor` will be the type `T` with all parameters removed: |
| 12 | +```jldoctest |
| 13 | +julia> struct T{A,B};a::A;b::B;end |
| 14 | +
|
| 15 | +julia> constructorof(T{Int,Int}) |
| 16 | +T |
| 17 | +``` |
| 18 | +It is however not guaranteed, that `ctor` is a type at all: |
| 19 | +```jldoctest |
| 20 | +julia> struct S |
| 21 | + a |
| 22 | + b |
| 23 | + checksum |
| 24 | + S(a,b) = new(a,b,a+b) |
| 25 | + end |
| 26 | +
|
| 27 | +julia> ConstructionBase.constructorof(S) = (a,b,checksum) -> (@assert a+b == checksum; S(a,b)) |
| 28 | +
|
| 29 | +julia> constructorof(S)(1,2) |
| 30 | +S(1, 2, 3) |
| 31 | +``` |
| 32 | +Instead `ctor` can be any object that satisfies the following properties: |
| 33 | +* It must be possible to reconstruct an object from its fields: |
| 34 | +```julia |
| 35 | +ctor = constructorof(typeof(obj)) |
| 36 | +@assert obj == ctor(fieldvalues(obj)...) |
| 37 | +@assert typeof(obj) == typeof(ctor(fieldvalues(obj)...)) |
| 38 | +``` |
| 39 | +* The other direction should hold for as many values of `args` as possible: |
| 40 | +```julia |
| 41 | +ctor = constructorof(T) |
| 42 | +fieldvalues(ctor(args...)) == args |
| 43 | +``` |
| 44 | +For instance given a suitable parametric type it should be possible to change |
| 45 | +the type of its fields: |
| 46 | +```jldoctest |
| 47 | +julia> using ConstructionBase: constructorof |
| 48 | +
|
| 49 | +julia> struct T{A,B};a::A;b::B;end |
| 50 | +
|
| 51 | +julia> t = T(1,2) |
| 52 | +T{Int64,Int64}(1, 2) |
| 53 | +
|
| 54 | +julia> constructorof(typeof(t))(1.0, 2) |
| 55 | +T{Float64,Int64}(1.0, 2) |
| 56 | +
|
| 57 | +julia> constructorof(typeof(t))(10, 2) |
| 58 | +T{Int64,Int64}(10, 2) |
| 59 | +``` |
| 60 | +""" |
| 61 | +@generated function constructorof(::Type{T}) where T |
| 62 | + getfield(parentmodule(T), nameof(T)) |
| 63 | +end |
| 64 | + |
| 65 | +function assert_hasfields(T, fnames) |
| 66 | + for fname in fnames |
| 67 | + if !(fname in fieldnames(T)) |
| 68 | + msg = "$T has no field $fname" |
| 69 | + throw(ArgumentError(msg)) |
| 70 | + end |
| 71 | + end |
| 72 | +end |
| 73 | + |
| 74 | +""" |
| 75 | + setproperties(obj, patch) |
| 76 | +
|
| 77 | +Return a copy of `obj` with attributes updates accoring to `patch`. |
| 78 | +
|
| 79 | +# Examples |
| 80 | +```jldoctest |
| 81 | +julia> using ConstructionBase |
| 82 | +
|
| 83 | +julia> struct S;a;b;c; end |
| 84 | +
|
| 85 | +julia> s = S(1,2,3) |
| 86 | +S(1, 2, 3) |
| 87 | +
|
| 88 | +julia> setproperties(s, (a=10,c=4)) |
| 89 | +S(10, 2, 4) |
| 90 | +
|
| 91 | +julia> setproperties((a=1,c=2,b=3), (a=10,c=4)) |
| 92 | +(a = 10, c = 4, b = 3) |
| 93 | +``` |
| 94 | +
|
| 95 | +There is also a convenience method, which builds the `patch` argument from |
| 96 | +keywords: |
| 97 | +
|
| 98 | + setproperties(obj; kw...) |
| 99 | +
|
| 100 | +# Examples |
| 101 | +```jldoctest |
| 102 | +julia> struct S;a;b;c; end |
| 103 | +
|
| 104 | +julia> o = S(10, 2, 4) |
| 105 | +S(10, 2, 4) |
| 106 | +
|
| 107 | +julia> setproperties(o, a="A", c="cc") |
| 108 | +S("A", 2, "cc") |
| 109 | +``` |
| 110 | +
|
| 111 | +# Overloading |
| 112 | +
|
| 113 | +**WARNING** The signature `setproperties(obj::MyType; kw...)` should never be overloaded. |
| 114 | +Instead `setproperties(obj::MyType, patch)` should be overloaded. |
| 115 | +""" |
| 116 | +function setproperties end |
| 117 | + |
| 118 | +function setproperties(obj; kw...) |
| 119 | + setproperties(obj, (;kw...)) |
| 120 | +end |
| 121 | + |
| 122 | +@generated function setproperties(obj, patch) |
| 123 | + assert_hasfields(obj, fieldnames(patch)) |
| 124 | + args = map(fieldnames(obj)) do fn |
| 125 | + if fn in fieldnames(patch) |
| 126 | + :(patch.$fn) |
| 127 | + else |
| 128 | + :(obj.$fn) |
| 129 | + end |
| 130 | + end |
| 131 | + Expr(:block, |
| 132 | + Expr(:meta, :inline), |
| 133 | + Expr(:call,:(constructorof($obj)), args...) |
| 134 | + ) |
| 135 | +end |
| 136 | + |
| 137 | +@generated function setproperties(obj::NamedTuple, patch) |
| 138 | + # this function is only generated to force the following check |
| 139 | + # at compile time |
| 140 | + assert_hasfields(obj, fieldnames(patch)) |
| 141 | + Expr(:block, |
| 142 | + Expr(:meta, :inline), |
| 143 | + :(merge(obj, patch)) |
| 144 | + ) |
| 145 | +end |
| 146 | + |
4 | 147 |
|
5 | 148 | end # module
|
0 commit comments