首先尝试使用以下模式(我假设MU Terran Terran
允许其他自我关系。):
foo (MU Terran Terran) = ...
foo (MU Terran Zerg) = ...
foo (MU Terran Protoss) = ...
foo (MU Zerg Zerg) = ...
foo (MU Zerg Protoss) = ...
foo (MU Protoss Protoss) = ...
foo (MU x y) = foo (MU y x)
你必须对这样定义的函数非常小心,因为如果你没有让案例详尽无遗,那就是一个无限循环。
第二次尝试:我尝试了概括这种模式,我想出的最好的就是这个,几乎没有更好的:
forceSymmetric :: (MU -> Maybe r) -> MU -> r
forceSymmetric f = \p -> case f p of
Nothing -> fromJust (f (swap p))
Just r -> r
foo (MU Terran Terran) = Just ...
foo (MU Terran Zerg) = Just ...
foo (MU Terran Protoss) = Just ...
foo (MU Zerg Zerg) = Just ...
foo (MU Zerg Protoss) = Just ...
foo (MU Protoss Protoss) = Just ...
foo (MU x y) = Nothing
这样做的好处是,如果你搞砸了,你会产生错误而不是无限循环。
第三,更深层次的尝试:问题的核心是你想要对称。让我们忘记这MU
是一个构造函数,而只是将其视为一个函数。你希望它遵守这个对称定律:
MU a b == MU b a
这里==
我不一定指Eq
类型类,而是相互替代;用一个表达式代替另一个表达式不应影响任何程序的含义。
好吧,代数数据类型没有那个属性,句号。对于代数数据类型构造函数,例如MU
,MU a b == MU c d
当且仅当a == b
和c == d
。所以如果你想让任何函数都无法区分MU Terran Zerg
and MU Zerg Terran
,你需要将MU
类型抽象化,这样它的用户就看不到它的内部表示。
一次取r的n 个项目的组合数的公式,允许重复,是;对于和,这是 6 种组合。所以我们想要的是定义一个只有六个可能值的类型,一个这样的函数,和一个这样的函数。我能想到的最简单的方法是使用排序的元组:factorial (n + r - 1) / (factorial r * factorial (n - 1))
n = 3
r = 2
MU
toMU :: Race -> Race -> MU
mu a b == mu b a
fromMU :: MU -> (Race, Race)
uncurry toMU . fromMU == id
data Race = Terran | Zerg | Protoss deriving (Eq, Show, Read, Ord);
data SortedPair a = SP a a -- The constructor here needs to be private
makeSortedPair :: Ord a => a -> a -> SortedPair a
makeSortedPair a b | a < b = SP a b
| otherwise = SP b a
breakSortedPair :: SortedPair a a -> (a, a)
breakSortedPair (SP a b) = (a, b)
type MU = SortedPair Race
toMU :: Race -> Race -> MU
toMU = makeSortedPair
fromMU :: MU -> (Race, Race)
fromMU = breakSortedPair
现在您可以保证fromMU
可以生产(Terran, Zerg)
但不能生产(Zerg, Terran)
,因此您可以从上面的前两个提案中省略最终的“包罗万象”案例。(然而,编译器对此一无所知,所以它仍然会抱怨非详尽的模式。)