F#区分联盟类型问题

时间:2016-06-28 16:56:41

标签: f# discriminated-union

我在让DU工作正常时遇到问题。我已经定义了一个新的DU,它的结果类型为<' a>或从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"))

我无法理解为什么它允许我创建一个&#34;错误&#34;如果我分两步完成,但是当我在一行上做同样的事情时,我会得到一个价值限制错误。

我缺少什么?

由于

更新

通过@kvb查看帖子,每次我需要创建一个错误时添加类型信息似乎有点冗长,所以我把它包装成另一个创建错误并且更简洁的方法。

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

我不确定是否用&#39;单位指定错误&#39;会有任何后果,我还没预见到。

TryResult<unit,_> = Error(err)

2 个答案:

答案 0 :(得分:5)

考虑这种轻微变化:

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

问题是看起来result3是一个值,但.NET类型系统不支持通用值,它只支持具体类型的值。因此,每次使用MyOtherException时都需要调用result3构造函数;然而,这会导致任何副作用不止一次发生,这将是令人惊讶的。正如Ringil建议的那样,您可以通过告诉编译器将表达式视为值来解决这个问题:

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

只要构造函数没有副作用,这很好。

答案 1 :(得分:2)

你可以这样做:

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

编辑:

至于为什么你不能一步完成,首先请注意这会导致同样的错误:

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

就像这样:

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

但这有效:

let result4 = Result ([1;])

与Exception和Arrays有什么相似之处,但不是列表?它的可变性。当您尝试使用可在一个步骤中变为可变的类型创建TryResult时,值限制将打扰您。

现在关于为什么两步过程解决了这个问题,因为构造函数使得整个函数不能推广,因为你将一个函数应用于构造函数。但是把它分成两个步骤可以解决这个问题。它类似于案例2 here on MSDN

您可以在上面的MSDN文章中了解更多相关信息,以及this more indepth blog post中发生这种情况的原因。