Suite of macros to automatically generate getters & setters for object properties, including corresponding hasproperty and propertynames methods.
Directly accessing properties of an object rather than defining an API can reduce namespace clutter (although one should definitely consider both approaches). Julia supports directly overriding default behavior of getproperty and setproperty!, but these methods are monolithic - i.e. you must differentiate between requested properties based on the property name. For large collections of virtual properties, this naturally separates properties' getters from their setters, reducing code clarity. These also do not add virtual properties to hasproperty and propertynames methods.
The @generate_properties macro provided by this library deals with all of these issues, albeit with some restrictions.
GenerateProperties exposes three macros: @generate_properties, @get, and @set, where the latter two do not have any use outside the scope of a @generate_properties block.
The library's syntax is rather simple:
@generate_propertyes <type> begin
@get <property> = <code_body>
@set <property> = <code_body>
endWhere you may, of course, order @get and @set declarations arbitrarily. As mentioned before, the @get and @set macros only function in tandem with the @generate_properties macro. Outside of one such they simply return nothing.
The syntax is designed to be reminiscent of "function assignment" syntax of Vanilla Julia. For examples, see below.
Note that code other than @get/@set will be silently swallowed.
GenerateProperties allows defining purely virtual properties. By example:
mutable struct MyStruct
size::NTuple{2, Float64}
end
@generate_properties MyStruct begin
@get width = self.size[1]
@set width = self.size = (value, self.size[2])
@get height = self.size[2]
@set height = self.size = (self.size[1], value)
end
let inst = MyStruct((24, 25))
inst.width # 24
inst.height # 25
inst.size # (24, 25)
inst.width = 42
inst.width # 42
inst.size # (42, 25)
hasproperty(inst, :width) # true
hasproperty(inst, :height) # true
hasproperty(inst, :size) # true
endBased on the enclosed @get and @set definitions, @generate_property automatically accumulates property names - both concrete fields and virtual properties - and automatically generates the corresponding property-related methods for your type.
@get and @set are designed to appear like property assignments - although they are really function bodies. Within code_body, one may refer to the current instance through the implicitly defined self::<type> argument. Within the code_body of a setter, one may access the implicitly defined value argument, which is the value to be assigned to the property.
Because self bypasses getproperty/setproperty!, you may override concrete fields as well. In doing so, omitting either a getter or setter will retain default Vanilla behavior. For example:
struct MyStruct
size::NTuple{2, Float64}
dirty::Bool
end
MyStruct(size = (0, 0)) = MyStruct(size, false)
@generate_properties MyStruct begin
@set size = (self.dirty = true; self.size = value)
end
let inst = MyStruct()
inst.size # (0, 0)
inst.dirty # false
inst.size = (24, 25)
inst.size # (24, 25)
inst.dirty # true
endGenerateProperties does (currently) not support hybrid usage, i.e. manually defining getproperty/setproperty! while using @generate_property. You must commit to either approach.
Using self within a getter/setter bypasses getproperty & setproperty! and directly calls getfield and setfield! instead - otherwise this could cause a stack overflow.