Совпадение шаблонов Haskell на GADT с видами данных

Я обнаружил, что мне очень нравится комбинировать GADT с Data Kinds, поскольку это дает мне дополнительную безопасность типов, чем раньше (для большинства применений, почти так же хорошо, как Coq, Agda et al.). К сожалению, совпадение шаблонов не выполняется на простейших примерах, и я не мог думать о том, чтобы не писать мои функции, кроме классов типов.

Вот пример, объясняющий мою печаль:

data Nat = Z | S Nat deriving Eq

data Le :: Nat -> Nat -> * where
    Le_base :: Le a a
    Le_S :: Le a b -> Le a (S b)

class ReformOp n m where
    reform :: Le (S n) (S m) -> Le n m

instance ReformOp a a where
    reform Le_base = Le_base

instance ReformOp a b => ReformOp a (S b) where
    reform (Le_S p) = Le_S $ reform p

class TransThm a b c where
    trans :: Le a b -> Le b c -> Le a c

instance TransThm a a a where
    trans = const

instance TransThm a a b => TransThm a a (S b) where
    trans Le_base (Le_S p) = Le_S $ trans Le_base p

instance (TransThm a b c, ReformOp b c) => TransThm a (S b) (S c) where
    trans (Le_S p) q = Le_S $ trans p $ reform q

У нас есть 2 типа классов (один для теоремы, один для служебной операции) и 5 ​​экземпляров - только для тривиальной теоремы. В идеале Haskell мог бы посмотреть на эту функцию:

-- not working, I understand why
trans :: Le a b -> Le b c -> Le a c
trans Le_base Le_base = Le_base
trans Le_base (Le_S p) = Le_S $ trans Le_base p
trans (Le_S p) q = Le_S $ trans p $ reform q

И самостоятельно проверяйте каждое предложение, и каждый вызов определяет, какие из них возможны (при этом стоит попробовать совместить), а какие нет, поэтому при вызове trans Le_base Le_base Haskell заметит, что только первый случай позволяет три переменные должны быть одинаковыми, и только попытка сопоставления по первому предложению.

Ответ 1

Я не вижу, как ваше определение соответствия шаблону trans будет работать в Agda или Coq.

Если вместо этого вы пишете следующее, оно работает:

reform :: Le (S n) (S m) -> Le n m
reform Le_base         = Le_base
reform (Le_S Le_base)  = Le_S Le_base
reform (Le_S (Le_S p)) = Le_S (reform (Le_S p))

trans :: Le a b -> Le b c -> Le a c
trans Le_base  q        = q
trans (Le_S p) Le_base  = Le_S p
trans (Le_S p) (Le_S q) = Le_S (trans p (reform (Le_S q)))

Конечно, вы также можете более точно определить:

trans :: Le a b -> Le b c -> Le a c
trans p Le_base  = p
trans p (Le_S q) = Le_S (trans p q)