2016-06-28 40 views
6

Sto riscontrando un problema nel far funzionare la DU come previsto. Ho definito un nuovo DU che o ha un risultato di tipo < 'a> o qualsiasi eccezione derivato da System.ExceptionF # Numero di tipo Unione discriminato

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")) 

non riesco a capire il motivo per cui mi permette di creare un 'errore', se Lo faccio in 2 passaggi, ma quando sto facendo la stessa cosa su una singola riga, ottengo un errore di restrizione del valore.

Cosa mi manca?

Grazie

UPDATE

Guardando il post di @kvb, aggiungendo informazioni sul tipo ogni volta che ho bisogno di creare un errore sembrava un po 'prolisso, così ho avvolto in su in un ulteriore metodo che crea un errore ed è un po 'più sintetico.

// 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")) 

io non sono sicuro se specificando un errore con 'unità' avrà alcuna conseguenza non ho ancora previsti.

TryResult<unit,_> = Error(err) 

risposta

5

considerare questo leggera variazione:

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... 

Il problema è che sembra result3 è un valore, ma il sistema di tipo .NET non supporta i valori generici, supporta solo i valori di tipi concreti. Pertanto, il costruttore MyOtherException deve essere chiamato ogni volta che viene utilizzato result3; tuttavia, questo comporterebbe effetti collaterali che si verificano più di una volta, il che sarebbe sorprendente.Come suggerisce Ringil, è possibile aggirare questo dicendo al compilatore di trattare l'espressione come un valore in ogni caso:

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

Questo va bene finché il costruttore non ha effetti collaterali.

+0

Grazie. Questo ha senso. Ho aggiornato la mia domanda aggiungendo un altro metodo di creazione degli errori che sembra funzionare correttamente e lo mantiene un po 'più ordinato. L'unica domanda è che il tipo è ora specificato come TryResult Non sono sicuro se questo avrà alcun down-side. – Moog

2

Si può fare:

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

EDIT:

Per quanto riguarda il motivo per cui non si può fare in un solo passaggio, prima nota che questo comporta lo stesso errore:

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

Nel modo seguente:

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

Ma che questo funziona:

let result4 = Result ([1;]) 

Cosa c'è di simile su Exception e Array, ma non elenchi? È la loro mutabilità . La restrizione del valore ti disturberà quando proverai a fare un TryResult con un tipo che è mutabile in un unico passaggio.

Ora come per il motivo per cui il processo in due passaggi risolve questo, è perché il costruttore rende l'intera funzione non generalizzabile perché si sta applicando una funzione al costruttore. Ma dividerlo in due passaggi risolve tutto ciò. È simile al caso 2 here on MSDN.

È possibile leggere ulteriori informazioni al riguardo nell'articolo MSDN sopra riportato e il motivo per cui ciò accade in this more indepth blog post.

+0

Ok, grazie. Questo è un modo per aggirare. Qualche idea del perché richiede questo parametro aggiuntivo mentre lo fa in 2 passaggi no? – Moog

+0

Ho aggiunto qualche spiegazione. – Ringil

+4

Non è che gli array siano mutabili ma le liste no - si tratta di ciò che è considerato una "espressione generalizzabile". Ad esempio, guarda cosa succede se provi 'Risultato ([1] @ [2])' o 'Risultato [| |] '. Inoltre, la tua soluzione non mi sembra troppo buona; la firma del tipo risultante è 'val result3 <'a>: TryResult ', che non è generale come desiderato; se segui questo percorso dovresti aggiungere un'annotazione di tipo. – kvb