2016-07-04 14 views
5

Próbuję utworzyć kolekcję z wieloma możliwymi typami. To jest przykład tego, jak chciałbym, aby wyglądał:Jak dokonać określonego typu należy do rodziny typów?

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" 

Chcę zadeklarować konkretne typy, które nadal mieszczą się pod ogólnym typem. Czy istnieje sposób, że mogę napisać mój kod w ten sposób? Czy istnieje bardziej idiomatyczny sposób na zrobienie tego?

+3

Czy rozumiesz, że Component.Position i Position nie są jednym i tym samym? –

Odpowiedz

6

Istnieje kilka rzeczy, które mają w swojej kod tutaj:

Rodzaj Skróty (Link)

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

Rodzaj skróty wystarczy zdefiniować inną nazwę dla istniejącego typu, rodzaju na w lewo i w prawo i dokładnie równoważne i mogą być używane zamiennie.

Aby podać przykład odnoszący się do tego pytania, w języku F # znajduje się skrót nazwy standardu .NET List, nazywa się to ResizeArray. Jest zdefiniowany następująco:

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

To oszczędza konieczności otwierania System.Collections.Generic w celu wykorzystania go i pomaga uniknąć nieporozumień z typem w F # list ale nie coś innego niż dodać nową nazwę zrobić istniejący typ.

Disciminated Związki (Link)

type Component = Position | Velocity | Appearance 

Tutaj masz jeden typ o nazwie Component, można myśleć o nim jako o jednym typie z trzech konstruktorów: Position, Velocity i Appearance. Możesz również ponownie zdekonstruować ten typ, używając tych samych trzech przypadków, dopasowując wzorzec.

np.

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

Mamy nadzieję, że powinna ona teraz dziwić, że skrót typ Position że zadeklarowana nie ma związku ze sprawą unii Position że zadeklarowane jako część typu Component. Są całkowicie niezależni od siebie.

Position oznacza Vector2 i Component jest całkowicie oddzielnym typem związku.

Zakładając, że chcesz mieć typ Component, który może zawierać wiele elementów, musisz powiązać niektóre wartości z przypadkami. Oto przykład utworzenia takiej unii dyskryminowanych:

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

Teraz spójrzmy na kolejnym problemem.

Gdybyśmy usunąć skróty typu i spróbować reszty kodu z naszym nowym dyskryminowanych Unii

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

Mamy teraz nowy błąd:

The type Position is not defined.

Cóż, pamiętam, co powiedziałem wcześniej około Component. Component jest typem, Position nie jest typem, jest to sprawa zbiorowa z Component.

Jeśli chciał zawierać całe słowniki tym samym jednym z tych opcji może być lepiej zmieniając definicję na coś takiego:

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

Następnie można utworzyć ResizeArray/List z nich.

let components = ResizeArray<ComponentDictionary>() 

Teraz, w celu zaludnionych tej kolekcji, to wtedy wystarczy użyć odpowiedniego konstruktora skrzynka dla ComponentDictionary

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

Teraz, poz jest typu ComponentDictionary więc możemy dodać go do:

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

Odpowiedź TheInnerLight jest bardzo kompletna. Chciałbym dodać, że może istnieć pewna korzyść z używania czystych typów F # (lista, mapa lub dict) zamiast ogólnej listy .Net i słownika. Oczywiście możesz potrzebować ogólnych kolekcji .NET. Oto prosty przykład, który wykorzystuje mapy (lub dict) i tworzy listę różnych składowych mapy:

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

I czy wystarczy listę (lub tablicy lub cokolwiek), która posiada różne typy elementów, które już mogą zrób to:

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