manywows
manywows

Reputation: 195

Ways to handle collections of interfaces with generic lambdas

I am currently trying to experiment with F# and I am struggling a bit with how to best handle a scenario such as described with these types:

[<Interface>]
type IStuff<'a> = 
    abstract member Update: 'a -> 'a

type Stuff1<'a> = {
    Pos : int 
    Sprite : string 
    Temperature : float 
    UpdateBehaviour  : 'a -> 'a 
} with 
    interface IStuff<'a> with
        member this.Update p = 
            this.UpdateBehaviour p
    static member New f = {
        Pos = 0
        Sprite = "Sprite"
        Temperature = 0.0
        UpdateBehaviour = f 
    }


type Stuff2<'a> = {
    Pos : int 
    UpdateBehaviour  : 'a -> 'a
} with 
    interface IStuff<'a> with
        member this.Update p = 
            this.UpdateBehaviour p
    static member New f = {
        Pos = 0
        UpdateBehaviour = f 
    }

Now ideally I would like to have both Stuff1 and Stuff2 types together in a collection where each particle would call its specific update function which may change based on the type (as types have varying fields of data) or simply differs between the same types.

I have tried multiple things with approaches such as this.

let stuffArr< 'T when 'T :> IStuff<'T>> = [|
    Stuff1.New<_> (fun p -> printfn "s1"; p)
    Stuff2.New<_> (fun p -> printfn "s2"; p)
|]

Which obviously does not function as the compiler identifies that these two are clearly different types with Stuff1 being type ´aand Stuff2 being type ´b.

I could also do an approach such as this.

let stuffArr : obj list = [
    Stuff1.New<_> (fun p -> printfn "p1"; p)
    Stuff2.New<_> (fun p -> printfn "p2"; p)
]

let iterateThrough (l : obj list) = 
    l
    |> List.map (fun p -> 
        let s = p :?> IStuff<_> 
        s.Update p)

Which functions obviously but as you all may now, I essentially turn off the type system and I am doing a dynamic down cast which frankly scares me.

So is there a better way to accomplish this? Thanks in advance!

Upvotes: 2

Views: 51

Answers (2)

torbonde
torbonde

Reputation: 2459

I get the feeling that what you're showing above may not have the same complexity as what you're actually trying to do. If that's the case, Tomas' solution may be too simple for you to use. If you have some reason to prefer using the interface you described above, you could define stuffArr as follows

let stuffArr : IStuff<_>[] = [|
    Stuff1.New<int> (fun p -> printfn "s1"; p)
    Stuff2.New<int> (fun p -> printfn "s2"; p)
|]

Here I assumed the generic parameter to be int. It could be any other type - I assume you have something in mind. However, the generic parameter for each entry in the array needs to be the same. If you need different generic parameters, you would need to wrap them in a discriminated union like so

type MyWrapper = String of string | Int of int
let stuffArr : IStuff<_>[] = [|
    Stuff1.New<MyWrapper> (fun p -> printfn "s1"; p)
    Stuff2.New<MyWrapper> (fun p -> printfn "s2"; p)
|]

or using the build-in Choice type

let stuffArr : IStuff<_>[] = [|
    Stuff1.New<Choice<string, int>> (fun p -> printfn "s1"; p)
    Stuff2.New<Choice<string, int>> (fun p -> printfn "s2"; p)
|]

Upvotes: 1

Tomas Petricek
Tomas Petricek

Reputation: 243061

There are ways to get a bit more checking with the structure you have, but none of those are very nice. However, I think that the fundamental issue is that you are trying to use F# as an object-oriented language. If you use a more functional design, this will look much nicer.

I would define Stuff as a discriminated union with the different kinds of stuff:

type Stuff = 
  | Stuff1 of pos:int * sprite:string * temperature:float
  | Stuff2 of pos:int

let stuffArr = [
  Stuff1(0, "Sprite", 0.0)
  Stuff2(0)
]

The update operation would then be just a function that uses pattern matching to handle the two different kinds of stuff you have:

let update stuff = 
  match stuff with 
  | Stuff1 _ -> ...
  | Stuff2 _ -> ...

The result is that you can update all stuff and get a new list using just List.map:

List.map update stuffArr

Upvotes: 4

Related Questions