解决枚举上不完整的模式匹配问题
在模式匹配时,是否有任何创造性的方法来解决 .NET 的“弱”枚举?我希望它们的功能与 DU 类似。这是我目前的处理方式。还有更好的想法吗?
[<RequireQualifiedAccess>]
module Enum =
let unexpected<'a, 'b, 'c when 'a : enum<'b>> (value:'a) : 'c = //'
failwithf "Unexpected enum member: %A: %A" typeof<'a> value //'
match value with
| ConsoleSpecialKey.ControlC -> ()
| ConsoleSpecialKey.ControlBreak -> ()
| _ -> Enum.unexpected value //without this, gives "incomplete pattern matches" warning
Are there any creative ways to work around .NET's "weak" enums when pattern matching? I'd like them to function similarly to DUs. Here's how I currently handle it. Any better ideas?
[<RequireQualifiedAccess>]
module Enum =
let unexpected<'a, 'b, 'c when 'a : enum<'b>> (value:'a) : 'c = //'
failwithf "Unexpected enum member: %A: %A" typeof<'a> value //'
match value with
| ConsoleSpecialKey.ControlC -> ()
| ConsoleSpecialKey.ControlBreak -> ()
| _ -> Enum.unexpected value //without this, gives "incomplete pattern matches" warning
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我认为总的来说这是一个艰巨的任务,正是因为枚举是“弱”的。
ConsoleSpecialKey
是“完整”枚举的一个很好的示例,其中分别由 0 和 1 表示的ControlC
和ControlBreak
是唯一有意义的它可以承担的价值观。但我们有一个问题,您可以将任何整数强制转换为ConsoleSpecialKey
!:因此您给出的模式确实不完整,确实需要处理。
(
更不用说诸如编辑:实际上,@ildjarn 指出 Flags 属性按照惯例用于区分完整枚举和位掩码枚举,尽管编译器赢了不能阻止您对未标记此属性的枚举使用按位运算,再次揭示枚举的弱点)。System.Reflection.BindingFlags
之类的更复杂的枚举,它们用于位掩码,但通过类型信息与简单枚举无法区分,这使情况进一步复杂化但是,如果您正在使用像
ConsoleSpecialKey
这样的特定“完整”枚举,并且一直编写最后一个不完整的模式匹配案例真的很困扰您,那么您始终可以创建一个完整的活动模式:然而,这类似于简单地不处理不完整的模式匹配情况并抑制警告。我认为你当前的解决方案很好,你只要坚持下去就好了。
I think in general this is a tall order, exactly because enums are "weak".
ConsoleSpecialKey
is a good example of a "complete" enum whereControlC
andControlBreak
, which are represented by 0 and 1 respectively, are the only meaningful values it can take on. But we have a problem, you can coerce any integer into aConsoleSpecialKey
!:So the pattern you gave really is incomplete and really does needs to be handled.
(
not to mention more complex enums likeedit: actually, @ildjarn pointed out that the Flags attribute is used, by convention, to distinguish between complete and bitmask enums, though the compiler won't stop you from using bitwise ops on an enum not marked with this attribute, again revealing the weakens of enums).System.Reflection.BindingFlags
, which are used for bitmasking and yet indistinguishable through type information from simple enums, further complicating the pictureBut if you are working with a specific "complete" enum like
ConsoleSpecialKey
and writing that last incomplete pattern match case all the time is really bugging you, you can always whip up a complete active pattern:However that's akin to simply leaving the incomplete pattern match case unhandled and suppressing the warning. I think your current solution is nice and you would be good just to stick with it.
根据斯蒂芬在评论中对他的回答提出的建议,我最终得到了以下解决方案。
Enum.unexpected
通过在前一种情况下抛出FailureException
和Enum.Unhandled 来区分无效枚举值和未处理的情况(可能是由于稍后添加枚举成员)
在后者中。示例
显然,它在运行时而不是编译时警告未处理的情况,但这似乎是我们能做的最好的事情。
Following the suggestion Stephen made in the comments to his answer, I ended up with the following solution.
Enum.unexpected
distinguishes between invalid enum values and unhandled cases (possibly due to enum members being added later) by throwing aFailureException
in the former case andEnum.Unhandled
in the latter.Example
Obviously, it warns about unhandled cases at run-time instead of compile-time, but it seems to be the best we can do.
我认为这是 F# 的一个功能,它迫使您处理枚举的意外值(因为可以通过显式转换来创建它们,并且因为程序集的更高版本可能会添加其他命名值)。你的方法看起来不错。另一种选择是创建一个活动模式:
这里与 UnhandledEnum 模式匹配的过程将抛出异常,但返回类型是可变的,因此无论是什么类型,它都可以在模式的右侧使用比赛归来。
I'd argue that it's a feature of F# that it forces you to handle unexpected values of an enum (since it is possible to create them via explicit conversions, and since additional named values may be added by later versions of an assembly). Your approach looks fine. Another alternative would be to create an active pattern:
Here the process of matching against the UnhandledEnum pattern will throw an exception, but the return type is variable so that it can be used on the right hand side of the pattern no matter what type is being returned from the match.
这是 F# 语言的一个小烦恼,而不是一个功能。可以创建无效的枚举,但这并不意味着 F# 模式匹配代码必须处理它们。如果由于枚举的值超出了定义范围而导致模式匹配失败,则错误不在模式匹配代码中,而在生成无意义值的代码中。因此,不考虑无效值的枚举上的模式匹配没有任何问题。
想象一下,如果按照相同的逻辑,F# 用户每次遇到 .Net 引用类型(可以为 null,就像枚举可以存储无效整数一样)时都被迫执行 null 检查。语言将变得无法使用。幸运的是,枚举出现得并不多,我们可以用 DU 来替代。
编辑:此问题现已通过 https://github.com/dotnet/fsharp/pull/ 解决4522,取决于用户手动添加#nowarn“104”。您将收到有关未匹配的定义 DU 案例的警告,但如果您已涵盖所有案例,则不会收到警告。
This is a minor annoyance of the F# language, not a feature. Invalid enums are possible to create, but that doesn't mean that F# pattern matching code should have to deal with them. If a pattern match fails because the enum took a value outside of the defined range, the error is not in the pattern match code but in the code that generated the meaningless value. Therefore there is nothing wrong with a pattern match on an enum that does not account for invalid values.
Imagine if, by the same logic, F# users were forced to do a null check every time they came across a .Net reference type (which can be null, just like an enum can store an invalid integer). The language would become unusable. Fortunately enums don't come up as much and we can substitute DUs.
Edit: this issue is now solved by https://github.com/dotnet/fsharp/pull/4522, subject to users adding #nowarn "104" manually. You will get warnings on unmached defined DU cases, but no warning if you have covered them all.