Я пытаюсь сформировать интуицию о том, когда (и сколько раз) вычисления на уровне типов с семействами типов "происходят" в Haskell. В качестве конкретного примера рассмотрим этот класс типов для индексации в n-арный продукт с использованием натурального уровня типа:
{-# LANGUAGE DataKinds, TypeOperators, KindSignatures, TypeFamilies, MultiParamTypeClasses,
ScopedTypeVariables, TypeApplications, AllowAmbiguousTypes
#-}
import Data.Kind (Type)
import Data.SOP (I(..),NP(..)) -- identity functor and n-ary product from "sop-core"
data N = Zero | Succ N -- to be used as a kind
class Key (i :: N) (ts :: [Type]) where
type Value i ts :: Type
getValue :: NP I ts -> Value i ts
instance Key Zero (t:ts) where
type Value Zero (t:ts) = t
getValue (I v :* _) = v
instance Key n ts => Key (Succ n) (t : ts) where
type Value (Succ n) (t:ts) = Value n ts
getValue (_ :* rest) = getValue @n rest
getValue' :: forall n ts. Key n ts => NP I ts -> Value n ts
getValue' = getValue @n @ts
getTwoValues :: forall n ts. Key n ts => NP I ts -> NP I ts -> (Value n ts, Value n ts)
getTwoValues np1 np2 = let getty = getValue @n @ts in (getty np1, getty np2)
main :: IO ()
main = do let np = I True :* I 'c' :* Nil
print $ getValue @(Succ Zero) np
print $ getValue' @(Succ Zero) np
print $ getTwoValues @(Succ Zero) np np
Моя интуиция заключается в том, что проверка типов при появлении getValue
в main
запускает «обход» списка уровня типов во время компиляции в поисках соответствующего типа значения Value (Succ Zero) '[Bool,Char]
. Этот обход может быть дорогостоящим для больших списков.
А как же getValue'
? Вызывает ли он «обход» списка уровней типов один раз, как прежде, или два раза, один для проверки самого getValue'
, а другой для проверки getValue
, от которого он зависит?
А как насчет getTwoValues
? В его подписи есть два вызова семейства типов Value n ts
, даже если они соответствуют одному и тому же типу. Они вычисляются независимо друг от друга, что замедляет компиляцию, или вычисление «разделяется» на уровне типа?