从F#DU访问特定案例

时间:2013-03-15 08:22:15

标签: f# discriminated-union

假设我有以下DU:

type Something =
| A of int
| B of string * int

现在我在这样的函数中使用它:

let UseSomething = function
| A(i) -> DoSomethingWithA i
| B(s, i) -> DoSomethingWithB s i

这是有效的,但我必须解构DU才能将它传递给DoSomethingWith *函数。尝试将DoSomethingWithA定义为:

,我觉得很自然
let DoSomethingWithA (a: Something.A) = ....

但编译器抱怨未定义类型A.

似乎完全符合F#的理念,想要将参数限制为Something.A,而不仅仅是任何旧的int,所以我只是以错误的方式去做?

3 个答案:

答案 0 :(得分:5)

需要注意的重要一点是AB相同类型Something的构造函数。因此,如果您尝试单独使用AB个案,您将获得无穷无尽的模式匹配警告。

IMO,解构所有DU案例是一个好主意,因为它是类型安全的,并迫使你想到处理这些案件,即使你不想这样做。如果您必须以相同的方式重复解构DU,则可能会出现此问题。在这种情况下,在DU上定义mapfold函数可能是个好主意:

let mapSomething fa fb = function
| A(i) -> fa i
| B(s, i) -> fb s i

请参阅@Brian的优秀Catamorphism series以了解有关DUs的弃牌。

那也说你的榜样很好。解构后,您真正处理的是stringint值。

您可以使用Active Patterns分别使用两种情况:

let (|ACase|) = function A i -> i | B _ -> failwith "Unexpected pattern B _"
let (|BCase|) = function B(s, i) -> (s, i) | A _ -> failwith "Unexpected pattern A _"

let doSomethingWithA (ACase i) = ....

但推断的doSomethingWithA类型仍然相同,并且在将B _传递给函数时会出现异常。所以做IMO是错误的。

答案 1 :(得分:4)

其他答案是准确的:在F#AB是构造函数,而不是类型,这是强类型函数语言(如Haskell或ML系列中的其他语言)采用的传统方法。但是,还有其他方法 - 我相信在Scala中,AB实际上是Something的子类,所以你可以使用那些更有意义的类型这样做。我不完全确定在设计决策中涉及哪些权衡,但一般来说,继承使得类型推断变得更难/不可能(并且对于Scala中的构造型类型推断而言比Haskell或ML语言更糟糕)。

答案 2 :(得分:3)

A不是类型,它只是Something的构造函数。你无法避免模式匹配,这不一定是件坏事。

那就是说,F#确实提供了一个名为活动模式的东西,例如

let (|AA|) = function 
    | A i -> i 
    | B _ -> invalidArg "B" "B's not allowed!"

然后您可以像这样使用:

let DoSomethingWithA (AA i) = i + 1

但是你没有理由想要这样做!你仍然可以在引擎盖下进行相同的旧模式匹配,并且冒着运行时错误的风险。

无论如何,UseSomething的实施对于F#来说是完全自然的。