简体   繁体   English

F#区分联盟类型问题

[英]F# Discriminated Union Type Issue

I'm having a problem getting my DU working as expected. 我正在按照预期让我的DU工作有问题。 I've defined a new DU which either has a result of type <'a> or any Exception derived from System.Exception 我已经定义了一个新的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"))

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 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. 通过@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"))

I'm not sure if specifying an Error with 'unit' will have any consequences I haven't foreseen yet. 我不确定用'单位'指定错误是否会产生任何我尚未预见到的后果。

TryResult<unit,_> = Error(err)

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. 问题是它看起来像result3是一个值,但.NET类型系统不支持通用值,它只支持具体类型的值。 Therefore, the MyOtherException constructor needs to be called each time result3 is used; 因此,每次使用result3都需要调用MyOtherException构造函数; 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: 正如Ringil建议的那样,您可以通过告诉编译器将表达式视为值来解决此问题:

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

This is fine as long as the constructor doesn't have side effects. 只要构造函数没有副作用,这就没问题。

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. 当您尝试使用可在一个步骤中变为可变的类型创建TryResult时,值限制将打扰您。

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 . 它类似于MSDN上的案例2。

You can read more about it at the above MSDN article and the why this happens in this more indepth blog post . 您可以在上面的MSDN文章中阅读更多相关信息,以及为什么会在这篇更为深入的博客文章中发生这种情况。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM