有没有办法说明运算符是可交换的,这样我就不必为两个方向给出相同的定义?例如:
data Nat = Zero | Succ Nat
(+) :: Nat -> Nat -> Nat
Zero + x = x
x + Zero = x
...
在这里,有没有一种方法可以让我不必同时给出这两个定义,其中一个会从另一个中暗示出来?有什么办法可以说明fn = flip fn
吗?
有没有办法说明运算符是可交换的,这样我就不必为两个方向给出相同的定义?例如:
data Nat = Zero | Succ Nat
(+) :: Nat -> Nat -> Nat
Zero + x = x
x + Zero = x
...
在这里,有没有一种方法可以让我不必同时给出这两个定义,其中一个会从另一个中暗示出来?有什么办法可以说明fn = flip fn
吗?
这个加法运算符不是必需的,但通常您可以通过添加一个翻转参数的最终方程来使函数可交换而无需实现所有翻转的情况:
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
但是,不利的一面是,如果您忘记处理一个案例,这很容易导致无限循环:
adjacent A B = True
adjacent B C = True
adjacent x y = adjacent y x
在这里,adjacent A C
会调用adjacent 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 会抱怨。
但我认为这只适用于具有大量案例的函数——否则,简单地枚举它们会更清楚。
把它作为一个答案:是的,如果你实现常规加法,你会自动结束一个交换操作:
(+) :: UInt -> UInt -> UInt
Zero + x = x
(Succ s) + x = s + (Succ x)
此操作是可交换的,尽管这两种方式都不是有效的,这意味着比加法运算符是以这种方式定义的要"big number as UInt" + Zero
花费更长的时间。Zero + "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
解决此问题的一种简单方法是按照您的方式定义加法,明确定义可交换性。