如何使一个特定类型属于一个类型族?

时间:2022-02-17 16:22:11

I'm trying to make a collection that holds multiple possible types. This is an example of how I'd want it to look:

我正在尝试创建一个集合,它包含多个可能的类型。这是我想要的一个例子:

type Position = Vector2
type Velocity = Vector2
type Appearance = String

type Component = Position | Velocity | Appearance

let components = List<Dictionary<string, Component>>()
let pos = Dictionary<string, Position>()

components.Add(pos) // Type "Position" does not match with type "Component"

I want to declare specific types that still fit under a general type. Is there a way that I can write my code like this? Is there a more idiomatic way to do this?

我想声明仍然适合通用类型的特定类型。我可以这样写代码吗?有更习惯的方法吗?

2 个解决方案

#1


6  

There are are a couple of things you have in your code here:

在你的代码中有一些东西:

Type Abbreviations (Link)

类型缩写(链接)

type Position = Vector2
type Velocity = Vector2
type Appearance = String

Type abbreviations just define another name for an existing type, the type on the left and right and precisely equivalent and can be used interchangeably.

类型缩写仅仅定义一个现有类型的另一个名称,左边和右边的类型精确地等效,可以互换使用。

To give an example pertinent to this question, in F# there is a type abbreviation for the standard .NET List, it's called ResizeArray. It's defined like this:

为了给出一个与这个问题相关的例子,在f#中有一个标准。net列表的类型缩写,叫做ResizeArray。它的定义是这样的:

type ResizeArray<'T> = System.Collections.Generic.List<'T>

It saves you having to open System.Collections.Generic in order to use it and it helps to avoids confusion with the list type in F# but it doesn't do anything other than add a new name for an existing type.

它使您不必打开System.Collections。为了使用泛型,它有助于避免与f#中的列表类型混淆,但它除了为现有类型添加新名称之外没有其他作用。

Disciminated Unions (Link)

Disciminated工会(链接)

type Component = Position | Velocity | Appearance

Here you have a single type called Component, you can think of it as a single type with three constructors: Position, Velocity and Appearance. You can also deconstruct the type again using the same three cases by pattern matching.

这里有一个称为组件的单一类型,您可以把它看作一个具有三个构造函数的单一类型:位置、速度和外观。您还可以通过模式匹配使用相同的三种情况再次解构该类型。

e.g.

如。

match comp with
|Position -> ..
|Velocity -> ..
|Appearance -> ..

Hopefully, it should now come as no surprise that the type abbreviation Position that you declared has no connection with the union case Position that you declared as part of the Component type. They are entirely independent of one another.

现在,您声明的类型缩写位置与作为组件类型的一部分声明的union case位置没有关联,这一点应该不会让人感到意外。他们彼此完全独立。

Position means Vector2 and Component is a completely seperate union type.

位置意味着向量2和组件是完全分离的联合类型。

Assuming you wanted a Component type that could contain multiple things, you would need to associate some values with the cases. Here is an example of creating such a Discriminated Union:

假设您想要一个可以包含多个内容的组件类型,那么需要将一些值与这些情况关联起来。这里有一个创建这样一个有区别的联盟的例子:

type Component = 
    | Position of Vector2
    | Velocity of Vector2
    | Appearance of string

Now, let's look at the next problem.

现在,我们来看下一个问题。

If we delete the type abbreviations and try the rest of the code with our new Discriminated Union

如果我们删除了类型缩写,并尝试使用新的有区别的Union来尝试其他代码。

let components = List<Dictionary<string, Component>>()
let pos = Dictionary<string, Position>()

We now have a new error:

我们现在有了一个新的错误:

The type Position is not defined.

类型位置没有定义。

Well, remember what I said earlier about Component. Component is the type, Position is not a type, it's a union case of Component.

记得我之前讲过的关于分量的内容。组件是类型,位置不是类型,它是组件的联合情况。

If you wanted to contain entire dictionaries of the same one of these options, you might be better changing your definition to something like this:

如果你想要包含所有这些选项中的一个,你最好把你的定义改成这样:

type ComponentDictionary =
    |PositionDictionary of Dictionary<string, Vector2>
    |VelocityDictionary of Dictionary<string, Vector2>
    |AppearanceDictionary of Dictionary<string, string>

Then you could create a ResizeArray/List of these.

然后可以创建一个ResizeArray/列表。

let components = ResizeArray<ComponentDictionary>()

Now, in order to populated this collection, we then just need use the appropriate case constructor for ComponentDictionary

现在,为了填充这个集合,我们只需要为ComponentDictionary使用适当的case构造函数

let pos = PositionDictionary (Dictionary<string, Vector2>())

Now, pos is of type ComponentDictionary so we can add it to components:

pos属于ComponentDictionary类型,我们可以将它添加到组件中:

components.Add(pos) // No error here!

#2


1  

TheInnerLight's answer is very complete. I would like to add that there might be some advantage of using pure F# types (list, map or dict) instead of the generic .Net List and Dictionary. Of course you might actually need the .NET generic collections. Here is a simple example which uses map (or dict) and creates a list of various Component maps:

内心之光的答案是非常完整的。我想补充一点,使用纯f#类型(列表、地图或字典)而不是通用的。net列表和字典可能有一些好处。当然,您可能需要。net泛型集合。下面是一个使用map(或dict)并创建各种组件映射列表的简单示例:

type Vector2 = float * float

type Component = 
    | Position of Vector2
    | Velocity of Vector2 
    | Appearance of string

type Components = list<Map<string,Component>> // this is not strictly necessary

let pos = Position(10.,20.)
let app = Appearance "here"

let compMap1= Map.empty<string,Component> // define an empty map for Component
let compMap1 = compMap1.Add("key1",pos).Add("key2",app) // or: let compMap1' = dict(["key1",pos;"key2",app])
let compMap2 = ["key3",pos;"key4",pos] |> Map.ofList // you can create map from a list
let (components:Components) = [compMap1;compMap2] // make a list of your maps
let components' = [compMap1;compMap2] // or just use a generic F# list
(*
val components' : Map<string,Component> list =
  [map [("key1", Position (10.0, 20.0)); ("key2", Appearance "here")];
   map [("key3", Position (10.0, 20.0)); ("key4", Position (10.0, 20.0))]]
*)

And if would just need a list (or Array, or whatever) that holds various component types you can already do it:

如果只需要一个包含各种组件类型的列表(或数组,或其他),您就可以这样做:

let compList = [pos;app]
//val compList : Component list = [Position (10.0, 20.0); Appearance "here"]

#1


6  

There are are a couple of things you have in your code here:

在你的代码中有一些东西:

Type Abbreviations (Link)

类型缩写(链接)

type Position = Vector2
type Velocity = Vector2
type Appearance = String

Type abbreviations just define another name for an existing type, the type on the left and right and precisely equivalent and can be used interchangeably.

类型缩写仅仅定义一个现有类型的另一个名称,左边和右边的类型精确地等效,可以互换使用。

To give an example pertinent to this question, in F# there is a type abbreviation for the standard .NET List, it's called ResizeArray. It's defined like this:

为了给出一个与这个问题相关的例子,在f#中有一个标准。net列表的类型缩写,叫做ResizeArray。它的定义是这样的:

type ResizeArray<'T> = System.Collections.Generic.List<'T>

It saves you having to open System.Collections.Generic in order to use it and it helps to avoids confusion with the list type in F# but it doesn't do anything other than add a new name for an existing type.

它使您不必打开System.Collections。为了使用泛型,它有助于避免与f#中的列表类型混淆,但它除了为现有类型添加新名称之外没有其他作用。

Disciminated Unions (Link)

Disciminated工会(链接)

type Component = Position | Velocity | Appearance

Here you have a single type called Component, you can think of it as a single type with three constructors: Position, Velocity and Appearance. You can also deconstruct the type again using the same three cases by pattern matching.

这里有一个称为组件的单一类型,您可以把它看作一个具有三个构造函数的单一类型:位置、速度和外观。您还可以通过模式匹配使用相同的三种情况再次解构该类型。

e.g.

如。

match comp with
|Position -> ..
|Velocity -> ..
|Appearance -> ..

Hopefully, it should now come as no surprise that the type abbreviation Position that you declared has no connection with the union case Position that you declared as part of the Component type. They are entirely independent of one another.

现在,您声明的类型缩写位置与作为组件类型的一部分声明的union case位置没有关联,这一点应该不会让人感到意外。他们彼此完全独立。

Position means Vector2 and Component is a completely seperate union type.

位置意味着向量2和组件是完全分离的联合类型。

Assuming you wanted a Component type that could contain multiple things, you would need to associate some values with the cases. Here is an example of creating such a Discriminated Union:

假设您想要一个可以包含多个内容的组件类型,那么需要将一些值与这些情况关联起来。这里有一个创建这样一个有区别的联盟的例子:

type Component = 
    | Position of Vector2
    | Velocity of Vector2
    | Appearance of string

Now, let's look at the next problem.

现在,我们来看下一个问题。

If we delete the type abbreviations and try the rest of the code with our new Discriminated Union

如果我们删除了类型缩写,并尝试使用新的有区别的Union来尝试其他代码。

let components = List<Dictionary<string, Component>>()
let pos = Dictionary<string, Position>()

We now have a new error:

我们现在有了一个新的错误:

The type Position is not defined.

类型位置没有定义。

Well, remember what I said earlier about Component. Component is the type, Position is not a type, it's a union case of Component.

记得我之前讲过的关于分量的内容。组件是类型,位置不是类型,它是组件的联合情况。

If you wanted to contain entire dictionaries of the same one of these options, you might be better changing your definition to something like this:

如果你想要包含所有这些选项中的一个,你最好把你的定义改成这样:

type ComponentDictionary =
    |PositionDictionary of Dictionary<string, Vector2>
    |VelocityDictionary of Dictionary<string, Vector2>
    |AppearanceDictionary of Dictionary<string, string>

Then you could create a ResizeArray/List of these.

然后可以创建一个ResizeArray/列表。

let components = ResizeArray<ComponentDictionary>()

Now, in order to populated this collection, we then just need use the appropriate case constructor for ComponentDictionary

现在,为了填充这个集合,我们只需要为ComponentDictionary使用适当的case构造函数

let pos = PositionDictionary (Dictionary<string, Vector2>())

Now, pos is of type ComponentDictionary so we can add it to components:

pos属于ComponentDictionary类型,我们可以将它添加到组件中:

components.Add(pos) // No error here!

#2


1  

TheInnerLight's answer is very complete. I would like to add that there might be some advantage of using pure F# types (list, map or dict) instead of the generic .Net List and Dictionary. Of course you might actually need the .NET generic collections. Here is a simple example which uses map (or dict) and creates a list of various Component maps:

内心之光的答案是非常完整的。我想补充一点,使用纯f#类型(列表、地图或字典)而不是通用的。net列表和字典可能有一些好处。当然,您可能需要。net泛型集合。下面是一个使用map(或dict)并创建各种组件映射列表的简单示例:

type Vector2 = float * float

type Component = 
    | Position of Vector2
    | Velocity of Vector2 
    | Appearance of string

type Components = list<Map<string,Component>> // this is not strictly necessary

let pos = Position(10.,20.)
let app = Appearance "here"

let compMap1= Map.empty<string,Component> // define an empty map for Component
let compMap1 = compMap1.Add("key1",pos).Add("key2",app) // or: let compMap1' = dict(["key1",pos;"key2",app])
let compMap2 = ["key3",pos;"key4",pos] |> Map.ofList // you can create map from a list
let (components:Components) = [compMap1;compMap2] // make a list of your maps
let components' = [compMap1;compMap2] // or just use a generic F# list
(*
val components' : Map<string,Component> list =
  [map [("key1", Position (10.0, 20.0)); ("key2", Appearance "here")];
   map [("key3", Position (10.0, 20.0)); ("key4", Position (10.0, 20.0))]]
*)

And if would just need a list (or Array, or whatever) that holds various component types you can already do it:

如果只需要一个包含各种组件类型的列表(或数组,或其他),您就可以这样做:

let compList = [pos;app]
//val compList : Component list = [Position (10.0, 20.0); Appearance "here"]