Как читать это "доказательство" GHC Core?

Я написал эту небольшую часть Haskell, чтобы выяснить, как GHC доказывает, что для натуральных чисел вы можете только вдвое уменьшить:

{-# LANGUAGE DataKinds, GADTs, KindSignatures, TypeFamilies #-}
module Nat where

data Nat = Z | S Nat

data Parity = Even | Odd

type family Flip (x :: Parity) :: Parity where
  Flip Even = Odd
  Flip Odd  = Even

data ParNat :: Parity -> * where
  PZ :: ParNat Even
  PS :: (x ~ Flip y, y ~ Flip x) => ParNat x -> ParNat (Flip x)

halve :: ParNat Even -> Nat
halve PZ     = Z
halve (PS a) = helper a
  where helper :: ParNat Odd -> Nat
        helper (PS b) = S (halve b)

Соответствующие части ядра станут:

Nat.$WPZ :: Nat.ParNat 'Nat.Even
Nat.$WPZ = Nat.PZ @ 'Nat.Even @~ <'Nat.Even>_N

Nat.$WPS
  :: forall (x_apH :: Nat.Parity) (y_apI :: Nat.Parity).
     (x_apH ~ Nat.Flip y_apI, y_apI ~ Nat.Flip x_apH) =>
     Nat.ParNat x_apH -> Nat.ParNat (Nat.Flip x_apH)
Nat.$WPS =
  \ (@ (x_apH :: Nat.Parity))
    (@ (y_apI :: Nat.Parity))
    (dt_aqR :: x_apH ~ Nat.Flip y_apI)
    (dt_aqS :: y_apI ~ Nat.Flip x_apH)
    (dt_aqT :: Nat.ParNat x_apH) ->
    case dt_aqR of _ { GHC.Types.Eq# dt_aqU ->
    case dt_aqS of _ { GHC.Types.Eq# dt_aqV ->
    Nat.PS
      @ (Nat.Flip x_apH)
      @ x_apH
      @ y_apI
      @~ <Nat.Flip x_apH>_N
      @~ dt_aqU
      @~ dt_aqV
      dt_aqT
    }
    }

Rec {
Nat.halve :: Nat.ParNat 'Nat.Even -> Nat.Nat
Nat.halve =
  \ (ds_dJB :: Nat.ParNat 'Nat.Even) ->
    case ds_dJB of _ {
      Nat.PZ dt_dKD -> Nat.Z;
      Nat.PS @ x_aIX @ y_aIY dt_dK6 dt1_dK7 dt2_dK8 a_apK ->
        case a_apK
             `cast` ((Nat.ParNat
                        (dt1_dK7
                         ; (Nat.Flip (dt2_dK8 ; Sym dt_dK6))_N
                         ; Nat.TFCo:R:Flip[0]))_R
                     :: Nat.ParNat x_aIX ~# Nat.ParNat 'Nat.Odd)
        of _
        { Nat.PS @ x1_aJ4 @ y1_aJ5 dt3_dKa dt4_dKb dt5_dKc b_apM ->
        Nat.S
          (Nat.halve
             (b_apM
              `cast` ((Nat.ParNat
                         (dt4_dKb
                          ; (Nat.Flip
                               (dt5_dKc
                                ; Sym dt3_dKa
                                ; Sym Nat.TFCo:R:Flip[0]
                                ; (Nat.Flip (dt_dK6 ; Sym dt2_dK8))_N
                                ; Sym dt1_dK7))_N
                          ; Sym dt_dK6))_R
                      :: Nat.ParNat x1_aJ4 ~# Nat.ParNat 'Nat.Even)))
        }
    }
end Rec }

Я знаю общий поток приведения типов через экземпляры семейства типов Flip, но есть некоторые вещи, которые я не могу полностью выполнить:

  • Какое значение @~ <Nat.Flip x_apH>_N? это экземпляр Flip для x? Как это отличается от @ (Nat.Flip x_apH)? Меня интересуют < > и _N

Что касается первого актера в halve:

  • Что означают dt_dK6, dt1_dK7 и dt2_dK8? Я понимаю, что это какие-то доказательства эквивалентности, но что такое?
  • Я понимаю, что Sym пробегает эквивалентность назад
  • Что делает ;? Достаточно ли применяются доказательства эквивалентности?
  • Что это за суффиксы _N и _R?
  • Есть ли TFCo:R:Flip[0] и TFCo:R:Flip[1] экземпляры Flip?

Ответ 1

@~ является приложением принуждения.

Угловые скобки обозначают рефлексивное принуждение их содержащегося типа с ролью, обозначенной подчеркнутой буквой.

Таким образом, <Nat.Flip x_ap0H>_N является доказательством равенства, что Nat.Flip x_apH равно номиналу Nat.Flip x_apH (как равные типы не только равных представлений).

У PS много аргументов. Мы смотрим на интеллектуальный конструктор $WPS, и мы видим, что первые два являются типами x и y соответственно. У нас есть доказательство того, что аргумент конструктора Flip x (в этом случае мы имеем Flip x ~ Even. Тогда мы имеем доказательства x ~ Flip y и y ~ Flip x. Конечным аргументом является значение ParNat x.

Теперь я пройду первый класс типа Nat.ParNat x_aIX ~# Nat.ParNat 'Nat.Odd

Начнем с (Nat.ParNat ...)_R. Это приложение-конструктор типов. Он поднимает доказательство x_aIX ~# 'Nat.Odd до Nat.ParNat x_aIX ~# Nat.ParNat 'Nat.Odd. R означает, что это означает, что это означает, что типы являются изоморфными, но не одинаковыми (в этом случае они одинаковы, но нам не нужны эти знания для выполнения трансляции).

Теперь рассмотрим основную часть доказательства (dt1_dK7 ; (Nat.Flip (dt2_dK8 ; Sym dt_dK6))_N; Nat.TFCo:R:Flip[0]). ; означает транзитивность, то есть применять эти доказательства по порядку.

dt1_dK7 является доказательством x_aIX ~# Nat.Flip y_aIY.

Если мы посмотрим на (dt2_dK8 ; Sym dt_dK6). dt2_dK8 показывает y_aIY ~# Nat.Flip x_aIX. dt_dK6 имеет тип 'Nat.Even ~# Nat.Flip x_aIX. Итак, Sym dt_dK6 имеет тип Nat.Flip x_aIX ~# 'Nat.Even и (dt2_dK8 ; Sym dt_dK6) имеет тип y_aIY ~# 'Nat.Even

Таким образом, (Nat.Flip (dt2_dK8 ; Sym dt_dK6))_N является доказательством того, что Nat.Flip y_aIY ~# Nat.Flip 'Nat.Even.

Nat.TFCo:R:Flip[0] является первым правилом флип, который является Nat.Flip 'Nat.Even ~# 'Nat.Odd'.

Соединяя их вместе, получаем (dt1_dK7 ; (Nat.Flip (dt2_dK8 ; Sym dt_dK6))_N; Nat.TFCo:R:Flip[0]) имеет тип x_aIX #~ 'Nat.Odd.

Второй более сложный состав немного сложнее выработать, но должен работать по тому же принципу.