假设我有以下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,所以我只是以错误的方式去做?
答案 0 :(得分:5)
需要注意的重要一点是A
和B
是相同类型Something
的构造函数。因此,如果您尝试单独使用A
和B
个案,您将获得无穷无尽的模式匹配警告。
IMO,解构所有DU案例是一个好主意,因为它是类型安全的,并迫使你想到处理这些案件,即使你不想这样做。如果您必须以相同的方式重复解构DU,则可能会出现此问题。在这种情况下,在DU上定义map
和fold
函数可能是个好主意:
let mapSomething fa fb = function
| A(i) -> fa i
| B(s, i) -> fb s i
请参阅@Brian的优秀Catamorphism series以了解有关DUs的弃牌。
那也说你的榜样很好。解构后,您真正处理的是string
和int
值。
您可以使用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#A
和B
是构造函数,而不是类型,这是强类型函数语言(如Haskell或ML系列中的其他语言)采用的传统方法。但是,还有其他方法 - 我相信在Scala中,A
和B
实际上是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#来说是完全自然的。