Parametric types & instances

I have ~20 trade types. Each has a rate-set struct and a rate-set-instance struct.
I’d like to define the rate-sets and rate-set-instances jointly (botom example).
There are ~3 rate-set field types and each has a unique rate-set-instance field type e.g.

rate_set        rate_set_instance
Currency        Float64
Interest_Rate   Interpolation{curve}
Volatility      Interpolation{surface}
struct deal_type_A_rate_set
    fx_rate::Currency
    ir_curve::Interest_Rate
end

struct deal_type_A_rate_set_instance
    fx_rate::Float64
    ir_curve::Interpolation
end

#could it be done like this
struct deal_type_A_rate_set{instance}
    FX_rate:: (instance ?  Float64      : Currency       )
    IR_curve::(instance ? Interpolation : InterestRate   )
end

If the issue is only factorizing code, wouldn’t it be better to do the reverse, having a type for sets and one for set instances, and have another type representing the ~20 trades, that parametrize the sets?

abstract type Trade end

struct Trade_A <: Trade end
struct Trade_B <: Trade end
# ...

struct RateSet{T<:Trade}
    fx_rate::Currency
    ir_curve::Interest_Rate
end

struct RateSetInstance{T<:Trade} 
    fx_rate::Float64
    ir_curve::Interpolation
end

You can define functions that use fx_rate regardless of which type the Rate has but they won’t be type stable:

function get_fx_rate(rs::Union{RateSet,RateSetInstance})
    return rs.fx_rate #  !!! Not type stable !!! return type is Currency or Float64
end

You can also define behavior for all trades or certain trades only using dispatch:

function generic_method_for_RateSet(rs::RateSet)
    # something on any RateSet
end

function compute_rate(ri::RateSetInstance{Trade_A})
    # something specific for Trade_A
end

If you really want the type of the field to change like you asked while keeping type concreteness, an option is to put the inner types as parameters:

struct GenericRateSet{T_FX, T_IR} 
    fx_rate::T_FX
    ir_curve::T_IR

    GenericRateSet{T_FX, T_IR}(fx_rate::T_FX, ir_curve::T_IR) 
        #you could check here that T_FX and T_IR are what you expect
        new{T_FX, T_IR}(fx_rate, ir_curve)
    end
end

and possibly use type aliases for dispatch/syntaxic sugar:

const RateSet = GenericRateSet{Currency,Interest_Rate}
const RateSetInstance = GenericRateSet{Float64,Interpolation}

But this second option in not very idiomatic, if two struct have different fields, make two different structs… Or use a generic struct with untyped field if there is no risk of performance issues.

You’d need to derive 2 more type parameters in the constructor. While it’s hypothetically possible for core, unchangeable operations like ?: to derive more parameters from fewer without method invalidation bugs, that’s not supported.

Do you really need to put them in one parametric type? X{true} and X{false} are disjoint subsets, could you just do a two-member Union?

1 Like

Since I’ll have ~20 of these. I didn’t want to define each twice.

struct deal_type_A_rate_set
    fx_rate::Currency
    ir_curve::Interest_Rate
end


# 20 other deal_type_rate_set structs...

struct deal_type_Q_rate_set
   fx_rate1::Currency
   fx_rate2::Currency
   ir_curve1::Interest_Rate
   ir_curve2::Interest_Rate
   fx_vol::FX_Volatility
end

The proposed union and parametric approaches both achieve this:

struct deal_type_A_rate_set
    fx_rate::Union{Currency,Float64}
    ir_curve::Union{Interest_Rate,Interpolation}
end

struct deal_type_A_rate_set{Tfx,Tir}
    fx_rate::Tfx
    ir_curve::Tir
end

I just wanted to be more explicit about whether a version was the ‘base’ or ‘instance’ version.

A VaR calc would use a struct array of 1,000 instances, which I’d like to put on a GPU. (the union approach might make this difficult)

Be carefull that the types of the fields of

struct deal_type_A_rate_set
    fx_rate::Union{Currency,Float64}
    ir_curve::Union{Interest_Rate,Interpolation}
end

are ambiguous (their concrete types cannot be inferred from the type of the struct), so it might not be performant, I’m not sure this corresponds to what @Benny suggested.

I’d like a parametric union

FX_parametric = {instance ? Float64         : Currency     }
IR_parametric = {instance ? Interpolation   : Interest_Rate}


struct deal_type_A_rate_set{instance}
    fx_rate::FX_parametric(instance)
    ir_curve::Interest_Rate(instance)
end

each parametric union type could be defined once then used in 20 deal_type_rate_sets