It’s not necessary for this addition operator, but in general you can make a function commutative without implementing all the flipped cases by adding a final equation that flips the arguments:
data X = A | B | C
adjacent A B = True
adjacent B C = True
adjacent A C = False
adjacent x y = adjacent y x -- covers B A, C B, and C A
However, the downside is that if you forget to handle a case, this easily leads to an infinite loop:
adjacent A B = True
adjacent B C = True
adjacent x y = adjacent y x
Here, adjacent A C
would call adjacent C A
, which would call adjacent A C
, and so on. And GHC’s pattern match exhaustivity checking (-fwarn-incomplete-patterns
or -Wall
) won’t help you here.
I guess you could add an additional argument to prevent looping:
data Commute = Forward | Reverse
adjacent = go Forward
where
go _ A B = True
go _ B C = True
go Forward x y = go Reverse y x -- try to commute
go Reverse _ _ = False -- commuting failed
Now GHC will complain if you don’t add the go Reverse
equation to handle the case where you commuted but there was still no match.
But I think this is only suitable for functions with a large number of cases—otherwise, it’s much clearer to simply enumerate them all.
f undefined x
can be different thanf x undefined
due to the fact that pattern matching is essentially sequential. This is the reason why it's so hard to obtain full abstraction between operational and denotational semantics of functional languages: in denotational semantics you include functions like parallel or etc which cannot actually be defined in the language. – Mendive