5

假设我有以下 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。

想要将参数限制为Something.A,而不仅仅是任何旧的int,这似乎完全符合F#的哲学,所以我只是走错了路吗?

4

3 回答 3

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 系列以了解有关 DU 的折叠。

这也说明你的例子很好。你真正处理的是解构后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是错误的。

于 2013-03-15T10:15:22.217 回答
4

其他答案是准确的:在 F#中A,并且B是构造函数,而不是类型,这是强类型函数语言(如 Haskell)或 ML 家族中的其他语言所采用的传统方法。但是,还有其他方法 - 例如,我相信在 Scala 中,A并且B实际上是 的子类Something,因此您可以在有意义的情况下使用那些更具体的类型。我不完全确定设计决策中涉及哪些权衡,但一般来说,继承使类型推断更难/不可能(Scala 中的原型类型推断比 Haskell 或 ML 语言更糟糕)。

于 2013-03-15T15:20:41.570 回答
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# 来说是非常自然的。

于 2013-03-15T10:16:15.743 回答