F # Union type discrimination

I have a problem with my DU working, as expected. I defined a new DU that either has a result of type <'a>, or any exception that is thrown from System.Exception

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

I can’t understand why it allows me to create β€œError” if I do it in 2 steps, but when I do the same in one line, I get a value limit error.

What am I missing?

thanks

UPDATE

Looking at the @kvb post, adding type information every time I needed to create an error seemed a bit verbose, so I included it in an extra method that creates an error and a bit more concise.

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

I am not sure if indicating an error with "unit" will have consequences that I have not yet foreseen.

 TryResult<unit,_> = Error(err) 
+6
source share
2 answers

Consider this small change:

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

The problem is that it looks like result3 , but a system like .NET does not support common values, it only supports values ​​of specific types. Therefore, the constructor MyOtherException must be called every time result3 used; however, this will lead to any side effects that will occur more than once, which would be surprising. As Ringil suggests, you can get around this by telling the compiler to treat the expression as a value anyway:

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

This is great if the constructor has no side effects.

+5
source

You can do:

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

EDIT:

As for why you cannot do this in one step, first note that this leads to the same error:

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

How does it do:

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

But it works:

 let result4 = Result ([1;]) 

What looks like Exception and Arrays but not Lists? This is their variability . Limiting the value will bother you when you try to make TryResult with a type that can be changed in one step.

Now, why the two-step process solves this, it is because the constructor makes the whole function non-generalizable, because you apply the function to the constructor. But splitting it into two steps solves this. This is similar to case 2 here on MSDN .

You can read more about this in the MSDN article above and why this is happening in this more detailed blog .

+2
source

All Articles