Home > Net >  Is it possible to have a list of generic types (from class hierarchy) with different type parameters
Is it possible to have a list of generic types (from class hierarchy) with different type parameters

Time:03-14

I have an Effect hierarchy described by a class hierarchy in F#. For simplicity, the classes look as follows:

[<AbstractClass>]
type Effect<'a, 'b>() = 
   class end

and EffectA<'a, 'b>() =
   inherit Effect<'a, 'b>() 

and EffectB<'a, 'b>() =
   inherit Effect<'a, 'b>()

The idea is that I will have a function to interpret such effects, where each effect will cause some kind of side effect in the system.

I would like to have a number of system threads, labeled worker threads, that each serve as an interpreter, i.e. each thread can interpret an effect indepedently of one another.

I would like to have a queue of such effects as shown above, indicating effects that are ready to be interpreted by the worker threads, such that the workers can fetch them and interpret them.

Now, my problem is, since these effects are generic on 'a and 'b, I am unable to have a single queue for all effects.

For example, this wouldn't work:

let add (eff : Effect<obj, obj>) (effs : Effect<obj, obj> list) =
    eff :: effs

let test =
    let effA = EffectA<int, string>()
    let effB = EffectB<string, float>()
    let effs = [effA]
    let newEffs = add effB effs
    ()

To recap: I would like a way to have a queue (or any kind of collection) of Effects<obj, obj> (or any other types, as long as they can be in the collection together) where ALL effects could be kept, and then some way to retrieve them from the list, and then cast them back to Effect<'a, 'b> with their original types.

I have been looking into using flexible types, but have so far been unsuccessful in trying.

I hope it makes sense. Thanks.

EDIT: @JL0PD suggested to add a non-generic base class, which is something I've attempted. It does actually work. The test function works as expected.

My question, however, with this approach is that how do I keep track of the original types of each effect, such that I can cast them correctly?

[<AbstractClass>]
type Effect() = class end

type Effect<'a, 'b>(a : 'a, b : 'b) = 
    inherit Effect()
    member _.A = a
    member _.B = b

and EffectA<'a, 'b>(a : 'a, b : 'b) =
   inherit Effect<'a, 'b>(a, b) 

and EffectB<'a, 'b>(a : 'a, b : 'b) =
   inherit Effect<'a, 'b>(a, b)

let add (eff : Effect) (effs : Effect list) =
    eff :: effs

let test =
    let effA = EffectA<int, string>(10, "abc")
    let effB = EffectB<string, float>("xyz", 1.0)
    let effs : Effect list = [effA]
    let newEffs = add effB effs
    let head = List.head newEffs :?> Effect<string, float>
    let otherEff = List.head (List.rev newEffs) :?> Effect<int, string>
    printfn $"head -> A: %A{head.A}, B: %A{head.B}"
    printfn $"otherEff -> A: %A{otherEff.A}, B: %A{otherEff.B}"
    ()

CodePudding user response:

I think that this might work. I am not sure if it is the most elegant solution, but it appears to do its job. If you know the types of generic parameters of the classes deriving from Effect, you can create a DU generic type that contains a union (set) of all types used across subtypes.


[<AbstractClass>]
type Effect<'a, 'b>() = 
   class end

and EffectA<'a, 'b>() =
   inherit Effect<'a, 'b>() 

and EffectB<'a, 'b>() =
   inherit Effect<'a, 'b>()

//create a DU type with all types that might appear in the DU elements
type EffectDU<'a, 'b, 'c, 'd> = 
    | EffectA of EffectA<'a, 'b> 
    | EffectB of EffectB<'c, 'd>


let add (eff : EffectDU<'a, 'b, 'c, 'd>) (effs : EffectDU<'a, 'b, 'c, 'd> list) =
    eff :: effs
   
let createTestList =
    let effA = EffectA (new EffectA<int, string>())
    let effB = EffectB (new EffectB<string, float>())
    let effC = EffectA (new EffectA<int, string>())
    let effs = [effA]
    let newEffs = [] |> add effA |> add effB |> add effC
    newEffs

let lst = createTestList

let processing = lst |> List.map (fun el -> match el with
                                            | EffectA a -> "Processing Effect A"
                                            | EffectB b -> "Processing Effect B"
                                            )

printfn "%A" processing 

This way you can avoid reflection and unwieldy type checking in your match expressions.

CodePudding user response:

So in the functional programming world you want something called existential types. It can be achieved (simulated really) without resorting to unsafe casts, but at the cost of a certain amount of boilerplate code.

The simplest mechanism to implement this in F# is to use the visitor design pattern, in some ways this mechanism is very similar to DUs but allows you to 'hide' your values type parameters behind the facade of the visited interface. (there are other variations of this, but lets stick to the OO pattern),

First you define the interface behind which your concrete types will 'hide', i.e. IEffect. Then define the vistor interface which will allow you to 'match' an IEffect. The each of the types you want to use to model your effects.

type IEffect = 
    abstract member AcceptVistor<'c> : IEffectVisitor<'c> -> 'c
and IEffectVisitor<'c> =
    abstract member VisitEffectA<'a,'b> : EffectA<'a,'b> -> 'c
    abstract member VisitEffectB<'a,'b> : EffectB<'a,'b> -> 'c
    abstract member VisitEffectC<'a,'b> : EffectC<'a,'b> -> 'c
and EffectA<'a,'b>() =
    member val Name = "EffectA"
    interface IEffect with
        member this.AcceptVistor v = 
            v.VisitEffectA this
and EffectB<'a,'b>() =
    member val Title = "EffectB"
    interface IEffect with
        member this.AcceptVistor v = 
            v.VisitEffectB this
and EffectC<'a,'b>() =
    member val Caption = "EffectC"
    interface IEffect with
        member this.AcceptVistor v = 
            v.VisitEffectC this

now you can define an array of 'IEffect's

let effects : IEffect[] = [| 
    EffectA<string,int>() :> IEffect
    EffectB<int,string>()
    EffectC<float,double>() |]

note all with different type parameters that become 'hidden'. The array is of the 'base' interface IEffect.

now you can process these effects

effects
|> Array.iter (fun effect -> 
    let message = 
        effect.AcceptVistor 
                { new IEffectVisitor<string> with 
                    member __.VisitEffectA eff = eff.Name
                    member __.VisitEffectB eff = eff.Title
                    member __.VisitEffectC eff = eff.Caption }
    printfn "%s" message)

Note there is no need to cast, the visitor 'knows' the type of each effect, including the type parameters.

this gives you a slightly more powerful mechanism than standard DUs, but with the costs of all the boilerplate (i.e. I wouldnt recommend using this mechanism unless you have to).

  • Related