Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a specific type belong to a family of types?

Tags:

.net

f#

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?

like image 816
Cookiemonstrosity Avatar asked Jul 04 '16 20:07

Cookiemonstrosity


1 Answers

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:

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.

Disciminated Unions (Link)

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.

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

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

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.

let components = ResizeArray<ComponentDictionary>()

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

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

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

components.Add(pos) // No error here!
like image 179
TheInnerLight Avatar answered Oct 19 '22 20:10

TheInnerLight