Естественным эталоном является статья Универсальное программирование с фиксированными точками для взаимно рекурсивных типов данных , где объясняется multirec пакет .
HFix
- тип с фиксированной точкойкомбинатор для взаимно рекурсивных типов данных.Это хорошо объяснено в разделе 3.2 в статье, но идея состоит в том, чтобы обобщить этот шаблон:
Fix :: (∗ -> ∗) -> ∗
Fix2 :: (∗ -> ∗ -> ∗) -> (∗ -> ∗ -> ∗) -> ∗
до
Fixn :: ((∗ ->)^n * ->)^n ∗
≈
Fixn :: (*^n -> *)^n -> *
Чтобы ограничить, сколько типов он делает фиксированной точкойони используют конструкторы типов вместо * ^ n.Они дают пример типа данных AST, взаимно рекурсивный по трем типам в статье.Вместо этого я предлагаю вам, пожалуй, самый простой пример.Давайте исправим этот тип данных:
data Even = Zero | ESucc Odd deriving (Show,Eq)
data Odd = OSucc Even deriving (Show,Eq)
Давайте введем семейный GADT для этого типа данных, как это сделано в разделе 4.1
data EO :: * -> * where
E :: EO Even
O :: EO Odd
EO Even
будет означать, что мы несемвокруг четного числа.Нам нужны экземпляры El для этого, чтобы указать, какой конкретный конструктор мы ссылаемся при написании EO Even
и EO Odd
соответственно.
instance El EO Even where proof = E
instance El EO Odd where proof = O
Они используются как ограничения для HFunctor
instance для I .
Давайте теперь определим функтор шаблона для четного и нечетного типа данных.Мы используем комбинаторы из библиотеки.Конструктор типа :>:
помечает значение своим индексом типа:
type PFEO = U :>: Even -- ≈ Zero :: () -> EO Even
:+: I Odd :>: Even -- ≈ ESucc :: EO Odd -> EO Even
:+: I Even :>: Odd -- ≈ OSucc :: EO Even -> EO Odd
Теперь мы можем использовать HFix
, чтобы связать узел вокруг этого функтора шаблона:
type Even' = HFix PFEO Even
type Odd' = HFix PFEO Odd
Теперь они изоморфны EO Even и EO Odd, и мы можем использовать функции hfrom
и hto
, если мы сделаем его экземпляром Fam
:
type instance PF EO = PFEO
instance Fam EO where
from E Zero = L (Tag U)
from E (ESucc o) = R (L (Tag (I (I0 o))))
from O (OSucc e) = R (R (Tag (I (I0 e))))
to E (L (Tag U)) = Zero
to E (R (L (Tag (I (I0 o))))) = ESucc o
to O (R (R (Tag (I (I0 e))))) = OSucc e
Простой маленький тест:
test :: Even'
test = hfrom E (ESucc (OSucc Zero))
test' :: Even
test' = hto E test
*HFix> test'
ESucc (OSucc Zero)
Еще один глупый тест с алгеброй, поворачивающей Even
и Odd
с к их Int
значению:
newtype Const a b = Const { unConst :: a }
valueAlg :: Algebra EO (Const Int)
valueAlg _ = tag (\U -> Const 0)
& tag (\(I (Const x)) -> Const (succ x))
& tag (\(I (Const x)) -> Const (succ x))
value :: Even -> Int
value = unConst . fold valueAlg E