7

Ich habe selten diesen Kampf heute mit F #, aber dann wieder Typ Vererbung ist viel seltener mit F #, also vielleicht hatte ich nur Glück. Oder ich vermisse das Offensichtliche. Normalerweise, wenn der Compiler sich darüber beschwert, dass ich einen bestimmten Typ nicht kenne, vertausche ich die Reihenfolge der Pipes oder Compositionsoperanden und ich bin fertig.Typ Rückschluss mit Rohrleitungen oder Zusammensetzung fehlschlägt, wo normale Funktionsaufruf erfolgreich ist

Grundsätzlich funktioniert ein Funktionsaufruf, der als g(f x) funktioniert, auch als x |> f |> g oder (f >> g) x. Aber heute ist es nicht ...

Hier ist ein unordentlicher Proof-of-concept von dem, was ich meine:

module Exc = 
    open System 

    type MyExc(t) = inherit Exception(t) 

    let createExc t = new MyExc(t) 
    type Ex = Ex of exn 
    type Res = Success of string | Fail of Ex with 
     static member createRes1 t = Ex(createExc(t)) |> Fail // compiled 
     static member createRes2 t = t |> createExc |> Ex |> Fail // FS0001 
     static member createRes3 = createExc >> Ex >> Fail // FS0001 

Normalerweise funktioniert die (zumindest in meiner Erfahrung). Die Zeilen mit "fail" throw:

Fehler FS0001: Typ stimmt nicht überein. Erwartet ein MyExc -> 'a aber ein exn gegeben -> Ex. Der Typ ‚MyExc‘ nicht mit dem Typ übereinstimmen ‚EXN‘

Keine große Sache, nicht schwer zu umgehen, aber ich zufällig haben eine Menge Code zu schreiben, wo Zusammensetzung ist das einfacher/Reiniger Ansatz und ich Ich möchte nicht eine Reihe von Utility-Funktionen schreiben, die ich überall einfügen muss.

Ich sah flexible Typen, wie ich denke, das ist ein Kontravarianz-Problem, aber ich sehe nicht, wie ich es hier anwenden kann. Irgendwelche Ideen, um diese Idiomatik zu behalten?

Hinweis, wenn ich neu anordnen, d. H. Als Ex <<createExc>> Fail oder mit der Pipe-Rückwärts-Operator ich am Ende mit dem gleichen Fehler auf einem anderen Teil.

Antwort

7

Der F # -Compiler verhält sich in diesem Fall etwas unregelmäßig. In Ihrem Beispiel möchten Sie einen Wert vom Typ MyExc an einen Konstruktor übergeben, der exn erwartet. Das Behandeln eines Objekts als Wert seiner Basisklasse ist eine gültige Kopie, aber der F # -Compiler fügt solche Anweisungen nur an sehr begrenzten Stellen ein.

Insbesondere wird Coersion eingefügt, wenn Sie Argumente an eine Funktion übergeben, diese jedoch nicht (zum Beispiel) beim Erstellen einer Liste oder beim Zurückgeben von Ergebnissen aus einer Funktion einfügen.

In Ihrem Beispiel benötigen Sie eine Coversion, wenn Sie den Wert an einen diskriminierten Union-Konstruktor übergeben. Es scheint, dass dies nur geschieht, wenn direkt die Vereinigung Fall zu schaffen, aber es funktioniert nicht passieren, wenn die Vereinigung Fall als Funktion der Behandlung:

// foo is a function that takes `obj` and Foo is a DU case that takes `obj` 
let foo (o:obj) = o 
type Foo = Foo of obj 

foo(System.Random()) // Coersion inserted automatically 
Foo(System.Random()) // Coersion inserted automatically 

System.Random() |> foo // Coersion inserted automatically 
System.Random() |> Foo // ..but not here! 

Also, die begrenzte Anzahl von Orten, an denen F # -Compiler coersions wendet automatisch schließt verschiedene Möglichkeiten zum Aufruf von Funktionen, aber nur direkte Möglichkeit zur Erstellung von DU-Fällen.

Das ist ein bisschen komisches Verhalten - und ich denke, dass es sinnvoll wäre, DU-Fälle als normale Funktionen einschließlich der automatischen Einfügung von Coersions zu behandeln, wenn Sie |> verwenden, aber ich bin nicht sicher, ob es irgendwelche technischen Gründe dafür gibt mach das hart.

+0

Ausgezeichnet und aufschlussreich, wie immer, danke Tomas. Scheint, als wären wir uns einig, dass dies sicherlich eine seltsame Art von Verhalten ist. Ich dachte immer (und normalerweise ist das richtig), dass sich DU-Konstruktoren wie Funktionen verhalten und ich sie ständig ketten/komponiere. Wenn sie die Unterschrift der DU-Mitglieder betrachten, sehen sie "ähnlich aus". – Abel

2

Sie könnten die Typen generisch mit einer Vererbungseinschränkung machen.

+0

Ja, das ist eine praktikable Lösung (vorausgesetzt, die Typen sind zugänglich). Ich hatte gehofft, besser zu verstehen, warum 'f x 'erfolgreich ist, aber' x |> f' nicht, oder wie man beide erfolgreich macht. Der Operator '|>' ist inline, also sollten diese Aufrufe gleichwertig sein? – Abel

3

Typ-Inferenz funktioniert nicht gut mit Subtyping (von denen Vererbung ein Fall ist).H & M Algorithmus hat keine Idee von Subtyping darin, und die verschiedenen Versuche, es im Laufe der Zeit anzupassen, haben keine guten Ergebnisse erbracht. Der F # -Compiler versucht sein Bestes, Subtyping in Form von Spezialfall-Patches, wo es möglich ist, unterzubringen. Zum Beispiel würde es die Funktion "kompatibel" betrachten, wenn das tatsächliche Argument ein Obertyp des formalen Parameters ist. Aus irgendeinem Grund wird dieser "Patch" jedoch nicht übersetzt, wenn Union-Konstruktoren in Funktionen konvertiert werden.

Zum Beispiel:

type U() = inherit exn() 
type T = T of exn 

let g f x = f x 

let e = U() 
let a = T e  // works 
let b = g T e  // compile error: `e` was expected to have type `exn`, but here has type `U` 

In der letzten Zeile, die Vereinigung Konstruktor T ist als kostenlose Funktion verwendet, so verliert er den Subtyping Patch.

Merkwürdigerweise funktioniert dies für reguläre Funktionen (dh diejenigen, die nicht als Vereinigung Bauer beginnen hat):

let makeT u = T u 
let a = makeT e  // works 
let b = g makeT e // also works! 

Und es funktioniert auch Punkt frei:

let makeT = T 
let a = makeT e  // works 
let b = g makeT e // still works! 

Dieses Detail schlägt einen Workaround für Sie vor: Sie können dem Ex Konstruktor nur einen anderen Namen geben, und die Verrohrung funktioniert:

type Ex = Ex of exn 
let makeEx = Ex 

    static member createRes2 t = t |> createExc |> makeEx |> Fail // Should work now 
Verwandte Themen