2016-07-06 4 views
1

Im folgenden Code möchte ich eine Template-Klasse mit dem Typ einer Variablen instanziieren, letztlich als generische Variable, die in eine Funktion übergeben wird, aber selbst diese einfachere Form funktioniert nicht:Verwendung von ableitbaren Typen zur Kompilierzeit in F # -Templates

type saveXY<'a when 'a:comparison> (x:'a,y:'a) = 
    member this.X = x 
    member this.Y = y 
    member this.lessThan() = this.X < this.Y 

[<EntryPoint>] 
let main argv = 

    let x1 = 3 
    let y1 = 7 
    let saver1 = new saveXY<int>(x1,y1)   // Good 
    printfn "%A" (saver1.lessThan()) 

    let x2 = 3.0 
    let y2 = 7.0 
    let saver2 = new saveXY<float>(x2,y2)   // Good 
    printfn "%A" (saver2.lessThan()) 

    let saver3 = new saveXY<x2.GetType()> (x2,y2) // No Good, see errors 

    0 

jedoch für saver3 unten ich (und ich kann Informationen über FS1241 finden):

...\Program.fs(23,39): error FS0010: Unexpected symbol '(' in type arguments. Expected ',' or other token. 
...\CompareProblem\Program.fs(23,39): error FS1241: Expected type argument or static argument 

Wenn Sie das Templat auf saveXY entfernen, dann ist saver2 irrtümlich als saver1 die 012 verursachtKlassenargumente, die auf Ints beschränkt sein müssen.

Ich experimentierte auch mit deklarieren x und y als einfach obj, aber das funktioniert nicht. Ich vermute, dass das Problem ist, dass dies einfach, dass dies nicht möglich ist, das heißt, wenn die Klassenargumenttypen generisch sind, werden sie einmal abgeleitet (von der ersten Verwendung). Auf der anderen Seite, vielleicht verpasse ich etwas.

Gibt es eine Möglichkeit, Variablen-basierte Typen zu verwenden, die zur Kompilierzeit als Typvorlagenargumente in F # definiert werden könnten? Gibt es eine andere Möglichkeit, einen Typ zu erstellen, der generische Werte verarbeiten/speichern kann?

UPDATE: Von Lee Vorschlag, das funktioniert, wenn Sie die Vorlage Klasse und dann nur <_> es zu instanziiert verwenden funktioniert:

type saveXY<'a when 'a:comparison> (x:'a, y:'a) = 
    member this.X = x 
    member this.Y = y 
    member this.lessThan() = this.X < this.Y 

[<EntryPoint>] 
let main argv = 

    let x1 = 3 
    let y1 = 7 
    let saver3 = new saveXY<_> (x1,y1) // works, 'a is int 
    printfn "%A" (saver3.lessThan()) 

    let x2 = 3.0 
    let y2 = 7.0 
    let saver3 = new saveXY<_> (x2,y2) // works, 'a is float 
    printfn "%A" (saver3.lessThan()) 

    System.Console.ReadKey() |> ignore // wait for a key 
    0 

Aber warum muss ich den Klassentyp als Templat, wie Ich schlug oben vor? Es scheint, dass der Compiler die Typen ohnehin herzuleiten wenn ich <_> also warum kann ich nicht einfach (wie ich würde für eine Funktion):

type saveXY(x, y) = // x and y are generic, no? They only require comparison, yes? 
    member this.X = x 
    member this.Y = y 
    member this.lessThan() = this.X < this.Y 

[<EntryPoint>] 
let main argv = 

    let x1 = 3 
    let y1 = 7 
    let saver3 = new saveXY (x1,y1) // works 
    printfn "%A" (saver3.lessThan()) 

    let x2 = 3.0 
    let y2 = 7.0 
    let saver3 = new saveXY (x2,y2) // But this FAILS with error FS0001 
    printfn "%A" (saver3.lessThan()) 

    System.Console.ReadKey() |> ignore // wait for a key 
    0 
+1

Nein, Sie können keine Typparameter wie diesen angeben, obwohl Sie den Typ von 'x2' und' y2' bereits statisch kennen, daher ist Ihr Anwendungsfall nicht klar. – Lee

+2

Nicht sehr klar, was genau du zu tun versuchst: Versuchst du Code zu erstellen, der deine Klasse mit einem generischen Argument instanziiert, das zum Kompilierungszeitpunkt nicht bekannt ist, oder willst du einfach sagen: "welcher Typ auch immer?" benutze bitte that_ ", und der Compiler hat es herausgefunden? –

+0

Es ist: _simply will sagen "welcher Typ x2 ist, bitte verwenden Sie das", und Compiler haben es herauszufinden? _ Fall - in dem obigen Beispiel möchte ich 'saver3' mit einem Float-Template-Typ instanziiert werden, da 'x2' ist ein Float. Im realen Code wird x2 ein Funktionsargument vom generischen Typ sein, aber wo die Funktion mit verschiedenen Kompilierzeittypen instanziiert wird – user1857742

Antwort

2

Wenn Sie nur den Compiler wollen die Art der folgern die generischen Parameter können Sie verwenden:

let saver3 = new saveXY<_>(x2, y2) 

oder

let saver3 = saveXY(x2, y2) 
+0

Das zweite Formular funktioniert nicht, aber das erste tut - siehe UPDATE – user1857742

+0

@ user1857742 - Welche Version von F # sind verwendest du? Die zweite Form könnte F # 4 – Lee

+0

Ich denke, es ist 3.1 - in Visual Studio 2013 – user1857742

2

F # hat keine integrierte Methode für generischen Code mit einer bekannten Art zur Laufzeit instanziieren. Sie können das immer über Reflektion tun, aber es ist nicht sehr nützlich - Sie werden nicht wirklich viel mit dem Wert tun können, wenn Sie es mit Reflektion erstellen.

Aus Gründen des Beispiels Sie LessThan in eine separate Schnittstelle bewegen konnte:

type ILessThan = 
    abstract LessThan : unit -> bool 

type SaveXY<'T when 'T:comparison> (x:'T,y:'T) = 
    member this.X = x 
    member this.Y = y 
    interface ILessThan with 
    member this.LessThan() = 
     printfn "Comparing values of type: %s" (typeof<'T>.Name) 
     this.X < this.Y 

Angesichts der Argumente ein System.Type und zwei obj Werte repräsentieren, Sie eine Instanz von SaveXY<T> zur Laufzeit mithilfe von Reflektion erstellen mit typeof<T> wobei die System.Type gegeben:

let createSaveXY typ x y = 
    let typ = typedefof<SaveXY<_>>.MakeGenericType [| typ |] 
    typ.GetConstructors().[0].Invoke([| x; y |]) :?> ILessThan 

Hier ist ein Beispiel dafür, wie dies funktioniert:

Aber wie ich schon sagte, das ist selten nützlich und es ist nicht sehr effizient - also anstatt dieses zu kopieren, versuchen Sie das Problem zu beschreiben, das Sie versuchen zu lösen - es gibt wahrscheinlich eine bessere Lösung.

+0

Vielen Dank für die detaillierte Antwort - Ich möchte nicht die Klasse mit einem Typ nur zur Laufzeit bekannt, sondern eine generische, die ein Argument ist eine Funktion. – user1857742