2016-06-28 7 views
6

Ich habe ein Problem damit, dass mein DU wie erwartet funktioniert. Ich habe ein neu MIR definiert, die entweder ein Ergebnis vom Typ hat < ‚a> oder jede Exception abgeleitet von System.ExceptionF # Discriminated Union Type Problem

open System 

// New exceptions. 
type MyException(msg : string) = inherit Exception(msg) 
type MyOtherException(msg : string) = inherit MyException(msg) 

// DU to store result or an exception. 
type TryResult<'a, 't> = 
    | Result of 'a 
    | Error of 't :> Exception 

//This is fine. 
let result = Result "Test" 

// This works, doing it in 2 steps 
let ex = new MyOtherException("Some Error") 
let result2 = Error ex 

// This doesn't work. Gives "Value Restriction" error. 
let result3 = Error (new MyOtherException("Some Error")) 

Ich kann nicht verstehen, warum es mir erlaubt, einen‚Fehler‘zu erstellen, wenn Ich mache es in 2 Schritten, aber wenn ich dasselbe in einer einzigen Zeile mache, erhalte ich einen Werteinschränkungsfehler.

Was fehlt mir?

Dank

UPDATE

von @kvb bei der Post Sehen, Hinzufügen von Informationen Typ jedes Mal, ich brauche ein bisschen ausführlicher, um einen Fehler zu schaffen schien so wickelte ich es in ein zusätzliches Verfahren auf die erstellt einen Fehler und ist ein wenig prägnanter.

// New function to return a Result 
let asResult res : TryResult<_,Exception> = Result res 

// New function to return an Error 
let asError (err : Exception) : TryResult<unit,_> = Error(err) 

// This works (as before) 
let myResult = Result 100 

// This also is fine.. 
let myResult2 = asResult 100 

// Using 'asError' now works and doesn't require any explicit type information here. 
let myError = asError (new MyException("Some Error")) 

Ich bin nicht sicher, ob ein Fehler mit ‚Einheit‘, wenn die Angabe keine Folgen haben werde ich noch nicht vorhersehbar.

TryResult<unit,_> = Error(err) 

Antwort

5

diese leichte Variation Bedenken Sie:

type MyOtherException(msg : string) = 
    inherit MyException(msg) 
    do printfn "%s" msg 

let ex = new MyOtherException("Some Error") // clearly, side effect occurs here 
let result2 = Error ex // no side effect here, but generalized value 

let intResults = [Result 1; result2] 
let stringResults = [Result "one"; result2] // can use result2 at either type, since it's a generalized value 

let result3 = Error (MyOtherException("Some Error")) // result would be of type TryResult<'a, MyOtherException> for any 'a 

// In some other module in a different compilation unit 
let intResults2 = [Result 1; result3]  // why would side effect happen here? just using a generic value... 
let stringResults2 = [Result "one"; result3] // likewise here... 

Das Problem ist, dass es aussieht wie result3 ein Wert ist, aber das System .NET-Typ unterstützt keine generische Werte, es unterstützt nur Werte von konkreten Typen. Daher muss der MyOtherException-Konstruktor jedes Mal aufgerufen werden, wenn result3 verwendet wird; dies würde jedoch dazu führen, dass irgendwelche Nebenwirkungen mehr als einmal auftreten, was überraschend wäre.Wie Ringil schon sagt, können Sie durch den Compiler zu sagen dies umgehen, den Ausdruck als Wert ohnehin zu behandeln:

[<GeneralizableValue>] 
let result3<'a> : TryResult<'a,_> = Error(new MyOtherException("Some Error")) 

Das ist in Ordnung, solange der Konstruktor keine Nebenwirkungen hat.

+0

Danke. Das macht Sinn. Ich habe meine Frage aktualisiert, indem ich eine zusätzliche Error-Erstellungsmethode hinzugefügt habe, die gut zu funktionieren scheint und sie etwas aufgeräumter hält. Die einzige Frage ist, dass der Typ jetzt als TryResult angegeben ist. Nicht sicher, ob dies eine Downside haben wird. – Moog

2

können Sie tun:

let result3<'a> = Error (new MyOtherException("Some Error")) 

EDIT:

Was, warum Sie es erste Note in einem Schritt nicht tun kann, dass dies in der gleichen Fehler führt:

let result4 = Result (new MyOtherException("Some Error")) 

Wie dies geschieht:

let result4 = Result ([|1;|]) 

aber, dass dies funktioniert:

let result4 = Result ([1;]) 

Was Exception und Arrays ähnlich ist, aber nicht Listen? Es ist ihre Wandlungsfähigkeit. Die Werteinschränkung stört Sie, wenn Sie versuchen, ein TryResult mit einem Typ zu erstellen, der in einem einzigen Schritt änderbar ist.

Nun, warum der Zwei-Schritt-Prozess dies löst, liegt daran, dass der Konstruktor die gesamte Funktion nicht generalisierbar macht, weil Sie eine Funktion auf den Konstruktor anwenden. Aber das Aufteilen in zwei Schritte löst das. Es ist ähnlich zu Fall 2 here on MSDN.

Sie können mehr darüber im oben genannten MSDN-Artikel und die Gründe dafür in this more indepth blog post lesen.

+0

Ok, danke. Das ist ein Workaround. Irgendeine Idee, warum es diesen zusätzlichen Parameter benötigt, während es in 2 Schritten tut, nicht? – Moog

+0

Ich habe eine Erklärung hinzugefügt. – Ringil

+4

Es ist nicht so, dass Arrays veränderbar sind, aber Listen nicht - es geht darum, was als "verallgemeinerbarer Ausdruck" betrachtet wird. Sehen Sie zum Beispiel, was passiert, wenn Sie 'Result ([1] @ [2])' oder 'Result [| |] '. Außerdem scheint mir Ihre Problemumgehung nicht zu gut; die resultierende Typ-Signatur ist "val result3 <'a>: TryResult ", was nicht so allgemein wie gewünscht ist; Wenn Sie diese Route gehen, sollten Sie einen Typ Annotation hinzufügen. – kvb