First try, use the following pattern (I'm assuming that MU Terran Terran
and other self-relations are allowed.):
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)
You have to be very careful with functions defined like that, because if you don't get the cases to be exhaustive it's an infinite loop.
Second try: I had a shot at generalizing the pattern, and the best I came up with is this, which is barely any better:
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
This has the virtue that you'd gen an error instead of an infinite loop if you mess up.
Third, deeper try: the heart of the problem is that you want symmetry. Let's forget that MU
is a constructor, and just treat it as a function. You want it to obey this symmetry law:
MU a b == MU b a
By ==
I don't necessarily mean the Eq
type class here, but rather mutual substitutibility; substituting one expression for the other should not affect the meaning of any program.
Well, algebraic data types don't have that property, period. For an algebraic data type constructor like MU
, MU a b == MU c d
if and only if a == b
and c == d
. So if you want to make it impossible for any function to distinguish between MU Terran Zerg
and MU Zerg Terran
, you need to make the MU
type abstract, so that its users cannot see its internal representation.
The formula for number of combinations of n items taken r at a time, with duplicates allowed, is factorial (n + r - 1) / (factorial r * factorial (n - 1))
; for n = 3
and r = 2
, this is 6 combinations. So what we want is to define a type MU
that has only six possible values, a function toMU :: Race -> Race -> MU
such that mu a b == mu b a
, and a function fromMU :: MU -> (Race, Race)
such that uncurry toMU . fromMU == id
. The easiest way I can think of doing this is to use sorted tuples:
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
Now you're guaranteed that fromMU
can produce (Terran, Zerg)
but not (Zerg, Terran)
, so you can leave out the final "catch-all" cases from the first two proposals above. (The compiler doesn't know anything about this, however, so it will still complain about non-exhaustive patterns.)