Haskell 运算符的交换 属性?

Commutative Property for Haskell Operators?

有没有一种方法可以说明运算符是可交换的,这样我就不必为两个方向给出相同的定义?例如:

data Nat = Zero | Succ Nat

(+) :: Nat -> Nat -> Nat
Zero + x = x
x + Zero = x
...

这里,有没有一种方法可以让我不必给出这两个定义,其中一个可以从另一个中隐含?有什么方法可以说明 fn = flip fn?

作为答案:是的,如果您实施常规加法,您会自动以交换操作结束:

(+) :: UInt -> UInt -> UInt
Zero + x = x
(Succ s) + x = s + (Succ x)

此操作是可交换的,尽管它不是双向有效的,这意味着 "big number as UInt" + ZeroZero + "big number as UInt" 花费的时间更长,因为加法运算符是这样定义的。

ghci> :set +s
ghci> bignum + Zero
number as uint
(0.01 secs, 4,729,664 bytes) -- inefficient O(n) operation
ghci> Zero + bignum
number as uint
(0.00 secs, 0 bytes) -- instant constant operation

解决此问题的一个简单方法是按照您的方式定义加法,显式定义交换性。

这个加法运算符不是必需的,但通常您可以通过添加翻转参数的最终方程来使函数可交换,而无需实现所有翻转的情况:

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

但是,缺点是如果忘记处理case,很容易导致死循环:

adjacent A B = True
adjacent B C = True
adjacent x y = adjacent y x

这里,adjacent A C会调用adjacent C Aadjacent C A会调用adjacent A C,依此类推。 GHC 的模式匹配穷举检查(-fwarn-incomplete-patterns-Wall)在这里帮不了你。

我想你可以添加一个额外的参数来防止循环:

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

现在如果你不添加 go Reverse 方程来处理你通勤但仍然没有匹配的情况,GHC 会抱怨。

不过我觉得这只适用于case比较多的函数,不然直接枚举一下就清楚多了