2016-04-18 15 views
2

Ich brauche folgende Beziehung zu beschreiben:F # Kovarianz und Kontra in Aufzeichnungen

type FieldDef<'Object, 'Val> { 
    Name: string; 
    Resolve: 'Object -> 'Val 
} 

type ObjectDef<'Object> { 
    Name: string; 
    Fields: FieldDef<'Object, _> list 
} 

// example usage 
type MyRecord = { X: int; Y: string; } 

let myRecordDef = { 
    Name = "MyRecord" 
    Fields = [ 
     { Name = "x"; Resolve: fun r -> r.X } 
     { Name = "y"; Resolve: fun r -> r.Y } 
    ] 
} 

Leider _ in diesem Anwendungsfall nicht gültig ist. Der zweite Param-Typ von FieldDef kann in diesem Fall variieren - in C# könnten wir ihn als kontravariant annotieren und an den Typ Object binden. Wie kann ich ein ähnliches Ergebnis in F # erreichen, ohne viel von der Typensicherheit zu verlieren?

+1

Warum nicht nur zwei Variablen vom Typ Objektdef nehmen machen –

+1

Wie ich schon sagte, der zweite Typ-Parameter kann unterschiedlich sein, d. H. Felder sollten sowohl 'Field ' und 'Field ' akzeptieren. Ich habe die Frage mit Beispiel aktualisiert, um den gewünschten Effekt besser zu beschreiben. – Horusiath

+1

Ich vermute, dass Sie in diesem Fall krank sind, ohne Typsicherheit. –

Antwort

2

Wie John sagt, wenn Sie vollständig generisch sein wollen, sind Sie mit Verlust der Typsicherheit fest. Ihre Objektdef Klasse ist dann

type ObjectDef<'Object> = 
    { 
     Name: string; 
     Fields: FieldDef<'Object, obj> list 
    } 

Ich weiß nicht „wie generic“ Sie sein müssen. Hier ist ein Vorschlag, wo man zumindest einige Feldtypen arbeitet explizit dargestellt: Sie definieren zunächst eine diskriminierte Union, den Austausch Ihres vollständig generisch 'Object -> 'Val:

type FieldGetter<'T> = 
    | Int of ('T -> int) 
    | String of ('T -> string) 
    | AnythingElse of ('T -> obj) 

Dann einen neuen Feld Definitionsdatensatz definieren, neben mit einem paar handlichen Überlastungen :

type FieldDef2<'Object> = 
    { 
     Name: string; 
     Resolve: FieldGetter<'Object> 
    } 
    static member Create(name, f) = { Name = name; Resolve = Int f } 
    static member Create(name, f) = { Name = name; Resolve = String f } 
    static member Create(name, f) = { Name = name; Resolve = AnythingElse f } 
type ObjectDef2<'Object> = 
    { 
     Name: string; 
     Fields: FieldDef2<'Object> list 
    } 

Ihr Beispiel verwandelt sich dann in:

let myRecordDef = { 
    Name = "MyRecord" 
    Fields = [ FieldDef2<_>.Create("x", fun r -> r.X); 
       FieldDef2<_>.Create("y", fun r -> r.Y) ] 
}