Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# Discriminated Union Type Issue

I'm having a problem getting my DU working as expected. I've defined a new DU which either has a result of type <'a> or any Exception derived 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 is allowing me to create an "Error" if I do it in 2 steps, but when i'm doing the same thing on a single line, I get a Value Restriction error.

What am i missing?

Thanks

UPDATE

Looking at the post by @kvb, adding type information each time I need to create an Error seemed a bit verbose, so I wrapped it up into an additional method which creates an Error and is a bit more succinct.

// 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'm not sure if specifying an Error with 'unit' will have any consequences I haven't foreseen yet.

TryResult<unit,_> = Error(err)
like image 644
Martin Cooper Avatar asked Jun 28 '16 16:06

Martin Cooper


2 Answers

Consider this slight variation:

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

The issue is that it looks like result3 is a value, but the .NET type system doesn't support generic values, it only supports values of concrete types. Therefore, the MyOtherException constructor needs to be called each time result3 is used; however, this would result in any side effects occurring more than once, which would be surprising. As Ringil suggests, you can work 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 fine as long as the constructor doesn't have side effects.

like image 74
kvb Avatar answered Sep 22 '22 02:09

kvb


You can do:

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

EDIT:

As for why you can't do it in one step, first note that this results in the same error:

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

As does this:

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

But that this works:

let result4 = Result ([1;])

What's similar about Exception and Arrays, but not Lists? It's their mutability. The value restriction will bother you when you try to do make a TryResult with a type that is mutable in a single step.

Now as for why the two step process solves this, it's because the constructor make the whole function not generalizable because you're applying a function to the constructor. But splitting it into two steps solves that. It is similar to Case 2 here on MSDN.

You can read more about it at the above MSDN article and the why this happens in this more indepth blog post.

like image 30
Ringil Avatar answered Sep 22 '22 02:09

Ringil