F# 区分联合类型问题

F# Discriminated Union Type Issue

我在让 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"))

我不明白为什么它允许我创建一个 "Error" 如果我分两步做,但是当我在一行上做同样的事情时,我得到一个值限制错误。

我错过了什么?

谢谢

更新

查看@kvb 的post,每次我需要创建一个错误时添加类型信息似乎有点冗长,所以我将它包装到一个额外的方法中,该方法创建一个错误并且有点更简洁。

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

我不确定用 'unit' 指定错误是否会产生我尚未预见的任何后果。

TryResult<unit,_> = Error(err)

你可以这样做:

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 有何相似之处,而 Lists 则不同?这是它们的 可变性 。当您尝试使用在单个步骤中可变的类型创建 TryResult 时,值限制会打扰您。

现在至于为什么两步过程解决了这个问题,这是因为构造函数使整个函数不可泛化,因为您正在将函数应用于构造函数。但是把它分成两步就可以解决这个问题。类似于案例2 here on MSDN.

您可以在上面的 MSDN 文章中阅读更多相关信息以及 this more indepth blog post.

中发生这种情况的原因

考虑这个细微的变化:

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

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

只要构造函数没有副作用就可以了。