Коммутативное свойство для операторов Haskell?

есть ли способ заявить, что оператор коммутативен, так что мне не нужно давать одинаковые определения для обоих направлений? Например:

data Nat = Zero | Succ Nat

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

вот, есть ли способ, чтобы мне не пришлось давать оба этих определения, что одно из них будет подразумеваться от другого? Есть ли способ, что fn = flip fn?

2 ответов


это не обязательно для этого оператора сложения, но в целом вы можете сделать функцию коммутативной без реализации всех перевернутых случаев, добавив окончательное уравнение, которое переворачивает аргументы:

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

теперь GHC будет жаловаться, если вы не добавите go Reverse уравнение для обработки случая, когда вы коммутировали, но все еще не было совпадения.

но я думаю, что это подходит только для функций с большим количеством случаев-в противном случае гораздо яснее просто перечислить их все.


чтобы поставить его в качестве ответа: Да, если вы реализуете регулярное сложение, вы автоматически получаете коммутативную операцию:

(+) :: 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

простой способ исправить это-определить сложение так, как вы это сделали, явно определяя коммутативность.