简体   繁体   English

F#是否有可能将歧视的工会价值“上调”为“超集”联盟?

[英]F# is it possible to “upcast” discriminated union value to a “superset” union?

Let's say there are two unions where one is a strict subset of another. 假设有两个联盟,其中一个是另一个的严格子集。

type Superset =
| A of int
| B of string
| C of decimal

type Subset =
| A of int
| B of string

Is it possible to automatically upcast a Subset value to Superset value without resorting to explicit pattern matching? 是否可以自动将Subset值向上转换为Superset值而无需借助显式模式匹配? Like this: 像这样:

let x : Subset = A 1
let y : Superset = x // this won't compile :(

Also it's ideal if Subset type was altered so it's no longer a subset then compiler should complain: 此外,如果Subset类型被更改,它是理想的,因此它不再是一个子集,然后编译器应该抱怨:

type Subset =
| A of int
| B of string
| D of bool // - no longer a subset of Superset!

I believe it's not possible to do but still worth asking (at least to understand why it's impossible) 我相信这是不可能做但仍然值得问(至少要理解为什么这是不可能的)

WHY I NEED IT 为什么我需要它

I use this style of set/subset typing extensively in my domain to restrict valid parameters in different states of entities / make invalid states non-representable and find the approach very beneficial, the only downside is very tedious upcasting between subsets. 我在我的域中广泛使用这种集合/子集类型来限制实体的不同状态中的有效参数/使无效状态不可表示并且发现该方法非常有益,唯一的缺点是子集之间非常繁琐的向上转换。

Sorry, no 抱歉,没有

Sorry, but this is not possible. 对不起,但这是不可能的。 Take a look at https://fsharpforfunandprofit.com/posts/fsharp-decompiled/#unions — you'll see that F# compiles discriminated unions to .NET classes, each one separate from each other with no common ancestors (apart from Object , of course). 看一下https://fsharpforfunandprofit.com/posts/fsharp-decompiled/#unions-你会看到F#将有区别的联合编译成.NET类,每个类都彼此分开,没有共同的祖先(除了Object ,当然)。 The compiler makes no effort to try to identify subsets or supersets between different DUs. 编译器不会尝试识别不同DU之间的子集或超集。 If it did work the way you suggested, it would be a breaking change, because the only way to do this would be to make the subset DU a base class, and the superset class its derived class with an extra property. 如果它按照你建议的方式工作,那将是一个重大改变,因为这样做的唯一方法是使子集DU成为基类,而超集类则使用额外属性作为派生类。 And that would make the following code change behavior: 这将使以下代码更改行为:

type PhoneNumber =
| Valid of string
| Invalid

type EmailAddress =
| Valid of string
| ValidButOutdated of string
| Invalid

let identifyContactInfo (info : obj) =
    // This came from external code we don't control, but it should be contact info
    match (unbox obj) with
    | :? PhoneNumber as phone -> // Do something
    | :? EmailAddress as email -> // Do something

Yes, this is bad code and should be written differently, but it illustrates the point. 是的,这是错误的代码,应该以不同的方式编写,但它说明了重点。 Under current compiler behavior, if identifyContactInfo gets passed a EmailAddress object, the :? PhoneNumber 在当前编译器行为下,如果identifyContactInfo传递了EmailAddress对象,则:? PhoneNumber :? PhoneNumber test will fail and so it will enter the second branch of the match, and treat that object (correctly) as an email address. :? PhoneNumber测试将失败,因此它将进入匹配的第二个分支,并将该对象(正确)视为电子邮件地址。 If the compiler were to guess supersets/subsets based on DU names as you're suggesting here, then PhoneNumber would be considered a subset of EmailAddress and so would become its base class. 如果编译器根据您在此处建议的DU名称猜测超集/子集,则PhoneNumber将被视为EmailAddress的子集,因此将成为其基类。 And then when this function received an EmailAddress object, the :? PhoneNumber 然后当这个函数收到EmailAddress对象时, :? PhoneNumber :? PhoneNumber test would succeed (because an instance of a derived class can always be cast to the type of its base class). :? PhoneNumber测试会成功(因为派生类的实例总是可以转换为其基类的类型)。 And then the code would enter the first branch of the match expression, and your code might then try to send a text message to an email address. 然后代码将进入匹配表达式的第一个分支,然后您的代码可能会尝试将短信发送到电子邮件地址。

But wait... 可是等等...

What you're trying to do might be achievable by pulling out the subsets into their own DU category: 通过将子集拉出到自己的DU类别中,您可以实现的目标是:

type AorB =
| A of int
| B of string

type ABC =
| AorB of AorB
| C of decimal

type ABD =
| AorB of AorB
| D of bool

Then your match expressions for an ABC might look like: 那么ABC匹配表达式可能如下所示:

match foo with
| AorB (A num) -> printfn "%d" num
| AorB (B s) -> printfn "%s" s
| C num -> printfn "%M" num

And if you need to pass data between an ABC and an ABD : 如果您需要在ABCABD之间传递数据:

let (bar : ABD option) =
    match foo with
    | AorB data -> Some (AorB data)
    | C _ -> None

That's not a huge savings if your subset has only two common cases. 如果您的子集只有两个常见情况,那么这不是一个巨大的节省。 But if your subset is a dozen cases or so, being able to pass those dozen around as a unit makes this design attractive. 但如果您的子集是十几个案例,那么能够将这十几个作为一个整体传递给这个设计会使这个设计具有吸引力。

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

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