Понимание приведений, используемых в шаблонах, соответствующих типу данных, который индексируется по определенному пользователю виду
Итак, я играл с DataKinds
и TypeFamilies
в Haskell и начал смотреть на генерируемый Core GHC.
Вот немного TestCase, чтобы мотивировать мой вопрос:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
module TestCase where
data Ty = TyBool | TyInt
type family InterpTy (t :: Ty) :: *
type instance InterpTy TyBool = Bool
type instance InterpTy TyInt = Int
data Expr (a :: Ty) where
I :: Int -> Expr TyInt
B :: Bool -> Expr TyBool
eval :: Expr a -> InterpTy a
eval (I i) = i
eval (B b) = b
Посмотрим на сгенерированное ядро для функции eval
TestCase.eval =
\ (@ (a_aKM :: TestCase.Ty)) (ds_dL3 :: TestCase.Expr a_aKM) ->
case ds_dL3 of _ [Occ=Dead] {
TestCase.I dt_dLh i_as6 ->
i_as6
`cast` (Sub (Sym TestCase.TFCo:R:InterpTyTyInt[0])
; (TestCase.InterpTy (Sym dt_dLh))_R
:: GHC.Types.Int ~# TestCase.InterpTy a_aKM);
TestCase.B dt_dLc b_as7 ->
b_as7
`cast` (Sub (Sym TestCase.TFCo:R:InterpTyTyBool[0])
; (TestCase.InterpTy (Sym dt_dLc))_R
:: GHC.Types.Bool ~# TestCase.InterpTy a_aKM)
}
Очевидно, нам нужно нести информацию о том, что a
может быть в конкретной ветки. Если я не индексирую Datakind и не буду использовать TypeFamilies, то это намного легче понять.
Это будет примерно так:
Main.eval =
\ (@ a_a1hg) (ds_d1sQ :: Main.Simpl a_a1hg) ->
case ds_d1sQ of _ [Occ=Dead] {
Main.I' dt_d1to i_aFa ->
i_aFa
`cast` (Sub (Sym dt_d1to) :: GHC.Integer.Type.Integer ~# a_a1hg);
Main.B' dt_d1tk b_aFb ->
b_aFb `cast` (Sub (Sym dt_d1tk) :: GHC.Types.Bool ~# a_a1hg)
}
Это я прекрасно понимаю, что меня беспокоит в примере TypeFamilies
:
(Sub (Sym TestCase.TFCo:R:InterpTyTyInt[0])
; (TestCase.InterpTy (Sym dt_dLh))_R
:: GHC.Types.Int ~# TestCase.InterpTy a_aKM);
Вторая строка - это то, что меня действительно смущает. Что такое точка с запятой? Кажется, это немного неуместно или я чего-то не хватает? Есть ли место, где я могу это прочитать (я с радостью беру документы, если вы можете порекомендовать его)
С уважением,
raichoo
Ответы
Ответ 1
Точка с запятой - синтаксис для транзитивности принуждений.
Последний (по состоянию на сентябрь 2014 года) документ о Системе FC "Безопасные принудительные издержки в стоимости Haskell" от ICFP 2014.
В частности, на рисунке 3 этой статьи мы видим синтаксис принуждений. "γ₁; γ₂" - транзитивная транзитивность. Если γ₁ является принуждением, свидетельствующим о том, что "τ₁ ~ τ₂" и γ₂ - это принуждение, которое свидетельствует о том, что τ₂ ~ τ₃, тогда "γ₁; γ₂" является принуждением, которое свидетельствует о τ₁ ~ τ₃.
В примере, когда вы пишете eval (I i) = i
то, что видит компилятор с правой стороны, это значение типа Int
, и то, что ему нужно (из возвращаемого типа функции), является чем-то вроде InterpTy a
, Итак, теперь нужно построить доказательство того, что Int ~ InterpTy a
.
Неформально (чтение справа налево и игнорирование ролей - подробности см. в связанной статье):
- Из выполнения соответствия шаблону GADT мы узнаем, что "
a ~ Int
" (это dt_dLh
)
- Поэтому мы применяем к нему
Sym
, чтобы получить "Int ~ a
".
- Затем примените семейство
InterpTy
, чтобы получить "InterpTy Int ~ InterpTy a
" (это экземпляр/конгруэнтности/)
- Затем мы составляем транзитивно с "
Sym InterpTyTyInt
" (это перевернутая версия аксиомы, которая гласит, что "InterpTy TyInt ~ Int
" ), чтобы получить принуждение, которое мы имеем после: "Int ~ InterpTy a
"