使用通用 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
}
现在理想情况下,我希望将 Stuff1
和 Stuff2
类型放在一个集合中,其中每个粒子将调用其特定的更新函数,该函数可能会根据类型而改变(因为类型有变化数据字段)或相同类型之间的不同。
我已经尝试过很多类似的方法。
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)
|]
我目前正在尝试使用 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
}
现在理想情况下,我希望将 Stuff1
和 Stuff2
类型放在一个集合中,其中每个粒子将调用其特定的更新函数,该函数可能会根据类型而改变(因为类型有变化数据字段)或相同类型之间的不同。
我已经尝试过很多类似的方法。
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)
|]