The onset Property

Knowing the onset of some time related event is useful for different analyses, but is encoded within different type structures in a way that may be incompatible for a single universal method. Here we define a property that has default functionality and then build on it for custom types. Throughout this process we try to ensure type stability.

Defining onset

Here we define the onset property and assume that if it's called on any subtype of AbstractArray that it the first axis refers to the time domain.

julia> using FieldProperties

julia> @defprop Onset{:onset} begin
           # assume the axis represents time
           @getproperty x::AbstractArray -> first(axes(x, 1))
       end

julia> onset(rand(2,2))
1

julia> @code_warntype (onset(rand(2,2)))
Variables
  #self#::Core.Compiler.Const(onset (generic function with 3 methods), false)
  x::Array{Float64,2}

Body::Int64
1 ─ %1 = Main.axes(x, 1)::Base.OneTo{Int64}
│   %2 = Main.first(%1)::Core.Compiler.Const(1, false)
└──      return %2

Custom Type

Now let's define a very basic type for described an event at a particular time.

julia> struct TimeEvent
           description::String
           onset::Int
       end

julia> te = TimeEvent("this time event", 2);

julia> onset(te)
2

# equivalent to...
julia> te.onset
2

julia> @code_warntype onset(te)
Variables
  p::Core.Compiler.Const(onset (generic function with 3 methods), false)
  x::TimeEvent

Body::Int64
1 ─ %1 = FieldProperties.getproperty(x, $(Expr(:static_parameter, 1)))::Int64
│   %2 = FieldProperties.propconvert(p, x, %1)::Int64
└──      return %2

Notice that we can still access the onset field here without any sort of method overloading. We also don't lose type stability. This is because our initial definition of the onset property only overloads the getproperty version for subtypes of AbstractArray. Therefore, the default method is simply getproperty(x, :onset), which is equivalent to x.onset for our TimeEvent type.

Optional Presence of onset

Now let's use the Metadata type to optionally store the onset property.

julia> m = Metadata();

julia> m.onset = 2
2

julia> onset(m)
2

julia> @code_warntype onset(m)
Variables
  p::Core.Compiler.Const(onset (generic function with 3 methods), false)
  x::Metadata{Dict{Symbol,Any}}

Body::Any
1 ─ %1 = FieldProperties.getproperty(x, $(Expr(:static_parameter, 1)))::Any
│   %2 = FieldProperties.propconvert(p, x, %1)::Any
└──      return %2

We can overcome problems with type stability by enforcing the returned type through onset_type (which was created with our original call to @defprop). We also can change the return type using this same method.

julia> onset_type(m::Metadata) = Float64
onset(eltype) (generic function with 3 methods)

julia> @code_warntype onset(m)
Variables
  p::Core.Compiler.Const(onset (generic function with 3 methods), false)
  x::Metadata{Dict{Symbol,Any}}

Body::Float64
1 ─ %1 = FieldProperties.getproperty(x, $(Expr(:static_parameter, 1)))::Any
│   %2 = FieldProperties.propconvert(p, x, %1)::Float64
└──      return %2

julia> onset(m)
2.0

This is functionally similar to writing onset(m::Metadata) = convert(Float64, getproperty(m, :onset)).