2013-05-23 15 views
9

Ich glaube, ich verstehe nicht ganz, wie F # Typen in Sequenzausdrücken und warum Typen nicht korrekt erkannt werden, auch wenn ich den Typ der Elemente direkt aus "Seq" angeben.Typ Rückschluss in Sequenzausdrücken in F #

Im folgenden F # -Code wir eine Basisklasse A und zwei abgeleiteten Klassen haben, B und C:

type A(x) = 
    member a.X = x 

type B(x) = 
    inherit A(x) 

type C(x) = 
    inherit A(x) 

Wenn ich versuche, „Ausbeute“ ihre Instanzen in einer einfachen Sequenz Ausdrücke, bekomme ich zwei Fehler : da es nicht so trivial sein kann

// Doesn't work, but it makes sense. 
let testSeq = seq { 
    yield A(0) 
    yield B(1) // Error, expected type: A 
    yield C(2) // Error, expected type: A 
} 

den Sinn machen kann, „gemeinsame“ Typen ableiten (Schnittstellen, glaube ich, kann diese Arbeit viel schwieriger machen). Allerdings können diese Fehler mit einem sicheren Guss festgesetzt:

// Works fine :) 
let testSeqWithCast = seq { 
    yield A(0) 
    yield B(1) :> A 
    yield C(2) :> A 
} 

Was passiert, wenn ich nicht will Abgüsse benutzen? Ich versuchte, die Sequenztyp direkt von „seq“ zu spezifizieren, aber die Dinge scheinen nicht zu funktionieren:

// Should work, I think... 
let testGenSeq = seq<A> { 
    yield A(0) 
    yield B(1) // Error, expected type: A 
    yield C(2) 
} 

Also, meine Frage ist: Gibt es eine Möglichkeit, Abgüsse zu vermeiden? Wenn nicht, gibt es einen Grund, warum die Angabe des Typs den Code nicht funktioniert?

Ich habe versucht, durch folgenden Links zu graben:

http://msdn.microsoft.com/en-us/library/dd233209.aspx http://lorgonblog.wordpress.com/2009/10/25/overview-of-type-inference-in-f/

Aber ich nichts brauchbares gefunden ...

Sie für jede Art von Antwort im Voraus Danke geben kann :)

Antwort

6

Um die Ursache Ihrer Verwirrung zu verstehen, sollten Sie nicht weiter gehen, als die erste Aussage von the link, die Sie bezogen haben:

Eine Sequenz ist eine logische Reihe von Elementen alle von einem Typ.

Sie können eine Folge von zurückkehren nur eine, die gleiche Typ wie seq<A> oder seq<obj>. Die OOP-ish-Tatsache, dass die Typen B und C von A übernommen werden, ist nicht relevant. Folgendes kann helfen: alle Instanzen auch von obj vererbt werden, aber um von ihnen sollten Sie eine seq<obj> machen explizit werfen: sie

// Works fine 
let testSeq = seq<obj> { 
    yield A(0) :> obj 
    yield B(1) :> obj 
    yield C(2) :> obj 
} 

oder box wie unten:

// Works fine too 
let testSeq = seq { 
    yield box (A(0)) 
    yield box (B(1)) 
    yield box (C(2)) 
} 

EDIT: Zum Verständnis der Gründe hinter expliziten Casting in F # kann die folgende (vereinfachende) Betrachtung helfen. Tippfehler raten nicht; es sei denn, es kann seq Typ deterministisch ableiten, oder es explizit deklariert haben, wird es sich beschweren.

Wenn Sie das tun nur

let testSeq = seq { 
    yield A(0) 
    yield B(1) 
    yield C(2) 
} 

Compiler mit indeterminism präsentiert wird - testSeq kann entweder seq<A> oder seq<obj>, so ist es beschwert. Wenn Sie

let testSeq = seq { 
    yield A(0) 
    yield upcast B(1) 
    yield upcast C(2) 
} 

tun folgert es testSeq als seq<A> nach Typ des ersten Elements und upcasts B und C zu A, ohne zu klagen. Und falls Sie

let testSeq = seq { 
    yield box A(0) 
    yield upcast B(1) 
    yield upcast C(2) 
} 

tun es testSeq als seq<obj> basierend auf dem Typ des ersten Elements schließen Upcasting dieser Zeit zweite und dritte Element zu obj, nicht A.

+1

Vielen Dank, übersah ich diese Aussage in der Dokumentation. Ich verstehe Ihren Standpunkt, aber es scheint mir immer noch eine seltsame Entscheidung zu sein ... Tatsächlich haben B und C A als Typ; Tatsächlich funktioniert der äquivalente Code, der in C# geschrieben ist (unter Verwendung von Rendite).In jedem Fall, nochmals vielen Dank für deine Antwort :) – pomma89

+0

Danke für die Änderungen, ich denke, dass die Situation jetzt klar ist :) Während ich meinen ersten Kommentar zu deiner Antwort geschrieben habe, habe ich widerwillig Enter gedrückt und eine "halb geschriebene" Antwort wurde eingereicht: Ich habe das sofort abgeschlossen, aber Sie haben vielleicht den Teil erhalten. Es tut mir Leid! – pomma89

+0

Ich habe meine Antwort mit einigen zusätzlichen Informationen bearbeitet, die helfen können, die explizite Upcasting-Regel in F # zu rechtfertigen. –

3

Es gibt keine implizite Upcasting in F # überprüfen here. Sie können das abgeleitete Upcasting versuchen.

let testSeq : seq<A> = seq { 
    yield A(0) 
    yield upcast B(1) 
    yield upcast C(2) 
    } 

Oder wenn es genügt, können Sie diskriminierte Gewerkschaften verwenden:

type X = 
    | A of int 
    | B of int 
    | C of int 

let testSeq = seq { 
    yield A 0 
    yield B 1 
    yield C 2 
    } 
+0

Danke für deine Antwort, aber der "echte" Code, mit dem ich mich befassen muss, ist teilweise in C# geschrieben. Daher kann ich den Typ von A, B, C nicht in eine Einheit "ändern", da sie in meinem Code in C# implementiert sind. In jedem Fall hat Ihre Antwort einige nützliche Tipps gegeben, die ich wirklich verwenden kann, danke! – pomma89

2

Der Fragesteller hat bereits eine Antwort akzeptiert hat, jedoch die folgenden nützlich sein. Zum Thema "Gibt es einen Weg, um Guss zu vermeiden" möchte ich hinzufügen: mit strikt seq ist die Antwort wie bereits gegeben (nicht möglich).

Sie könnten jedoch Ihren eigenen "Workflow" schreiben. Etwas wie:

open Microsoft.FSharp.Collections; 

    let s = seq<string> 

    type A(x) = 
     member a.X = x 

    type B(x) = 
     inherit A(x) 

    type C(x) = 
     inherit A(x) 

    type MySeq<'a>() = 
    member this.Yield(item: 'a): seq<'a> = 
     Seq.singleton item 
    member this.Yield(item: 'b): seq<'a> = 
     Seq.singleton ((item :> obj) :?> 'a) 
    member this.Combine(left, right) : seq<'a> = 
     Seq.append left right 
    member this.Delay (fn: unit -> seq<'a>) = fn() 

    [<EntryPoint>] 
    let main argv = 

     let myseq = new MySeq<A>() 
     let result = myseq { 
     yield A(1) 
     yield B(2) 
     } 

     0 

Beachten Sie, dass diese Antwort nicht besonders Kompilierung sicher ist, nicht ganz sicher, ob das getan (nervtötende generische Constraints) werden.

7

Dies ist eine gute Frage, und die Antwort ist wahrscheinlich komplizierter als die Antworten, die Sie bisher erhalten haben. So funktionierts:

let l : A list = [A(0); B(1); C(2)] 

aber dieser scheinbar analog Code nicht:

let s : A seq = seq { yield A(0); yield B(1); yield C(2) } 

Der Grund ist eigentlich sehr subtil. Der zweite Fall desugars, an dem etwas ist im Grunde eine kompliziertere Version von:

let s : A seq = 
    Seq.append (Seq.singleton (A(0))) 
       (Seq.append (Seq.singleton (B(1))) 
          (Seq.singleton (C(2))))) 

Also, was ist das Problem? Letztendlich ist das Problem, dass Seq.singleton hat generischen Typ 'x -> 'x seq, aber wir wollen eine B übergeben und erhalten Sie eine A seq im zweiten Aufruf (durch implizite Upcasting der Instanz). F # wird implizit eine Funktion Eingabe eines Beton Typ in einen Basistyp Beton upCast (zum Beispiel, wenn Seq.singleton Unterschrift hatte A -> A seq wir konnte eine B passieren!). Leider tritt dies bei generischen Funktionen nicht auf (Generika, Vererbung und Typinferenz spielen nicht gut zusammen).

+0

Vielen Dank, Ihre Antwort gibt einen sehr guten Einblick, wie das Problem komplizierter ist als es scheint. Sobald ich Zeit habe, werde ich meine Frage bearbeiten, und ich werde eine "Zusammenfassung" aller Ihrer Antworten hinzufügen, so dass zukünftige Leser schnell finden können, was sie suchen. Nochmals vielen Dank :) – pomma89

+0

@Doktor_P Wenn Sie zusammenfassen möchten, ist es empfehlenswert, entweder eine andere Antwort zu bearbeiten oder Ihre eigene zu veröffentlichen. Die Antwort auf die Frage zu setzen, verwirrt die Dinge. – mydogisbox

0

Dies ist nur eine Zusammenfassung aller Antworten, die meine Frage erhalten hat, so dass zukünftige Leser ihre Zeit sparen können, indem sie dies lesen (und entscheiden, ob sie andere Antworten lesen oder nicht, um einen besseren Einblick zu erhalten).

Die kurze Antwort auf meine Frage, wie @Gene Belitski darauf hingewiesen hat, ist nein, es ist nicht möglich, Umwandlungen in dem beschriebenen Szenario zu vermeiden. Zu allererst die Dokumentation selbst bestimmt:

eine Sequenz eine logische Reihe von Elementen alle von einem Typ.

Darüber hinaus ist in einer Situation wie der nächste:

type Base(x) = 
    member b.X = x 

type Derived1(x) = 
    inherit Base(x) 

type Derived2(x) = 
    inherit Base(x) 

Wir haben sicher, dass eine Instanz von Derived1 oder von Derived2 ist auch eine Instanz von Base, aber es ist auch wahr, dass diese Instanzen sind auch Instanzen von obj. Daher wird in dem folgenden Beispiel:

let testSeq = seq { 
    yield Base(0) 
    yield Derived1(1) // Base or obj? 
    yield Derived2(2) // Base or obj? 
} 

Wir haben, dass, wie @Gene Belitski erläutert, kann der Compiler nicht die richtige Vorfahre zwischen Base und obj wählen. Eine solche Entscheidung kann mit Umwandlungen wie im folgenden Code unterstützt werden:

let testBaseSeq = seq<Base> { 
    yield Base(0) 
    yield upcast Derived1(1) 
    yield upcast Derived2(2) 
} 

let testObjSeq = seq<obj> { 
    yield Base(0) :> obj 
    yield Derived1(1) :> obj 
    yield Derived2(2) :> obj 
} 

Es gibt jedoch mehr zu erklären. Wie @kvb feststellt, liegt der Grund, warum dies ohne Umwandlungen nicht funktionieren kann, darin, dass wir Generika, Vererbung und Typinferenz implizit mischen, was möglicherweise nicht so gut funktioniert wie erwartet. Das Snippet, in der testSeq erscheint automatisch in umgewandelt wird:

let testSeq = Seq.append (Seq.singleton (Base(0))) 
         (Seq.append (Seq.singleton (Derived1(1))) 
            (Seq.singleton (Derived2(2)))) 

Das Problem liegt in Seq.singleton, wo eine automatische upcast (wie in Seq.singleton (Derived1(1))) erforderlich wäre, aber es kann nicht getan werden, da Seq.singleton generisch ist. Wenn die Signatur Seq.singleton beispielsweise Base -> Base seq gewesen wäre, dann hätte alles funktioniert.

@Marcus schlägt eine Lösung für meine Frage vor, die darauf hinausläuft, meinen eigenen Sequence Builder zu definieren. Ich habe versucht, den folgenden Builder schreiben:

type gsec<'a>() = 
    member x.Yield(item: 'a) = Seq.singleton item 
    member x.Combine(left, right) = Seq.append left right 
    member x.Delay(fn: unit -> seq<'a>) = fn() 

Und das einfache Beispiel I scheint gebucht werden adaequat:

type AnotherType(y) = 
    member at.Y = y 

let baseSeq = new gsec<Base>() 
let result = baseSeq { 
    yield Base(1)  // Ok 
    yield Derived1(2) // Ok 
    yield Derived2(3) // Ok 
    yield AnotherType(4) // Error, as it should 
    yield 5    // Error, as it should 
} 

ich auch versucht, den Customizer erstreckt, so dass es komplexere Konstrukte wie for unterstützt und while, aber ich habe es versäumt, den while-Handler zu schreiben. Das kann eine nützliche Forschungsrichtung, wenn jemand interessiert ist.

Dank an alle, die beantwortet :)