使用通用 lambda 处理接口集合的方法

Ways to handle collections of interfaces with generic lambdas

我目前正在尝试使用 F# 进行试验,但我正在努力解决如何最好地处理这些类型所描述的场景:

[<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 
    }

现在理想情况下,我希望将 Stuff1Stuff2 类型放在一个集合中,其中每个粒子将调用其特定的更新函数,该函数可能会根据类型而改变(因为类型有变化数据字段)或相同类型之间的不同。

我已经尝试过很多类似的方法。

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

这显然不起作用,因为编译器识别出这两个明显不同的类型,Stuff1´a 类型,Stuff2´b 类型。

我也可以采用这样的方法。

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)

它的功能很明显,但正如你们现在可能看到的那样,我基本上关闭了类型系统,我正在做一个动态的向下转换,坦率地说,这让我感到害怕。

那么有没有更好的方法来实现这个呢?提前致谢!

有很多方法可以对您的结构进行更多检查,但是 none 其中的方法非常好。但是,我认为根本问题是您正在尝试将 F# 用作面向对象的语言。如果你使用更实用的设计,这将看起来更好。

我将 Stuff 定义为具有不同种类东西的有区别的联合:

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

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

更新操作将只是一个函数,它使用模式匹配来处理您拥有的两种不同类型的东西:

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

结果是您可以更新所有内容并使用 List.map:

获取新列表
List.map update stuffArr

我觉得您上面展示的内容可能没有您实际尝试做的那样复杂。如果是这样的话,Tomas 的解决方案可能对您来说太简单了,无法使用。如果您出于某些原因更喜欢使用上面描述的界面,您可以按如下方式定义 stuffArr

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

这里我假设泛型参数是int。它可以是任何其他类型 - 我假设你有一些想法。但是,数组中每个条目的通用参数必须相同。如果您需要不同的通用参数,则需要将它们包装在一个可区分的联合中,就像这样

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)
|]

或使用内置 Choice 类型

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