Type.Reflection和Nats的泛型类型

时间:2018-02-15 15:27:20

标签: haskell generics types

我有一个简单的Nat定义和Nat?索引的类型定义。

data Nat :: * where 
    Zero :: Nat
    Suc  :: Nat -> Nat              deriving(Show, Typeable)

data Natty :: Nat -> * where
    Zy :: Natty Zero
    Sy :: Natty n -> Natty (Suc n)  deriving Typeable

我希望使用Type.Reflection模块以通用方式存储和操作这些Natty。

作为Nat存储作为类型代表工作正常。

foo :: Nat -> SomeTypeRep
foo x = SomeTypeRep (typeOf x)

但是,除非添加额外的约束,否则将Natty存储为类型代表并不起作用。

boo :: Natty n -> SomeTypeRep
boo x = SomeTypeRep (typeOf x)

boo会产生以下错误:

No instance for (Typeable n) arising from the use of 'typeOf'

我的问题是为什么Haskell不能识别n是nat,因此应该是Typeable?

就像我说的,我可以通过将类型n的约束添加到boo的开头来解决这个问题,但是当我使用在另一个函数的中间返回natty的函数时就不那么简单了。

已使用以下扩展和导入。

{-# LANGUAGE DataKinds, KindSignatures, GADTs #-} 
import Type.Reflection

2 个答案:

答案 0 :(得分:1)

添加Typeable n反映了GHC需要将Typeable字典传入boo的情况,如果调用n实例化不同的话。

围绕TypeableDataKinds的相互作用有一些聪明之处。 GHC派生的实例大致相当于

instance Typeable Nat where ...
instance Typeable 'Zero where ...
instance Typeable n => Typeable ('Suc n) where ...

Typeable n实例不会填写提示您添加的Typeable Nat约束(请记住nNat种,而不是* }!),但是受后两种约束。

但是,我可以听到你说,为什么GHC可以通过对这两个实例的简单归纳来确定所有n :: Nat都是Typeable?因为'Zero'Suc不是制作某种类型的唯一方式Nat ...

ghci> type family Stuck :: k
ghci> :kind Natty Stuck          -- a valid type!
Natty Stuck :: *

换句话说,GHC在编译时不能知道n :: NatTypeable,你需要将它传递给这个事实的运行时见证。

答案 1 :(得分:1)

您可以实现所需的功能,但不是免费的。

boof :: Natty n -> TypeRep n
boof Zy = typeRep
boof (Sy n) = withTypeable (boof n) typeRep

更明确地说明(这基本上是boof内部发生的事情):

goo :: Natty n -> TypeRep n
goo Zy = typeRep @'Zero
goo (Sy n) = App (typeRep @'Suc) (goo n)

goo所示,TypeRep的结构实际上与Natty的结构非常相似。实际上,你也可以采取其他方式,尽管你无法说服模式检查者总是:

hum :: TypeRep n -> Natty n
hum (App s n)
  | Just HRefl <- eqTypeRep s (typeRep @'S) = Sy (hum n)
  | otherwise = error "imp possible"
hum z
  | Just HRefl <- eqTypeRep z (typeRep @'Z) = Zy
  | otherwise = error "impposs ible"

您可以按照自己的意愿结束结果:

boo :: Natty n -> SomeTypeRep
boo n = SomeTypeRep (boof n)

但似乎你应该使用比SomeTypeRep更具体的东西。 Typeable内部实际上使用了这个:

data SomeKindedTypeRep k where
  SomeKindedTypeRep :: forall (x :: k). Typeable x => SomeKindedTypeRep k

您可以定义并使用SomeKindedTypeRep Nat,或仅为Nat定义专用版本。这样你就不会因为你故意忘记这种类型而忘记那种(在编译时就知道)。