类型声明中的类型限制

时间:2019-06-26 17:08:54

标签: haskell type-systems gadt data-kinds phantom-types

有一个著名的类型级自然数示例:

data Zero
data Succ n

当我们应用类型构造器Succ时,我有一个关于理想限制的问题。例如,如果要对函数定义进行此类限制,则可以使用类和上下文,如以下代码所示:

class Nat n where
  toInt :: n -> Int
instance Nat Zero where
  toInt _ = 0
instance (Nat n) => Nat (Succ n) where
  toInt _ = 1 + toInt (undefined :: n)

不可能使用toInt (undefined :: Succ Int),没关系。

但是如何实现对类型级别构造的类似限制(也许带有一些高级类型扩展)?

例如,我只允许将类型构造函数Succ与类型Zero一起使用,例如:Succ (Succ Zero)Succ (Succ (Succ Zero))等。 如何避免在编译时出现此类不良示例:

type Test = Succ Int

(目前,没有编译错误)

P.S .:对我来说,更有趣的是如何对最后一个样本的类型声明创建限制

1 个答案:

答案 0 :(得分:5)

现在,我们使用DataKinds扩展名:

{-# LANGUAGE DataKinds, KindSignatures, ScopedTypeVariables #-}

-- N is a type, but is also a kind now
-- Zero, Succ Zero, ... are values, but are also type-level values of
-- kind N
data N = Zero | Succ N

-- (We could import Proxy the library, instead)
data Proxy (n :: N) = Proxy

-- Now n is restricted to kind N
class Nat (n :: N) where
  toInt :: proxy n -> Int

instance Nat Zero where
  toInt _ = 0
instance (Nat n) => Nat (Succ n) where
  toInt _ = 1 + toInt (undefined :: Proxy n)

然后我们可以使用toInt (Proxy :: Proxy (Succ Zero))。相反,toInt (Proxy :: Proxy (Succ Int))会根据需要引发一种错误。

就个人而言,我还可以将代理替换为更现代的东西,例如AllowAmbiguousTypesTypeApplications,以便删除未使用的参数。

{-# LANGUAGE DataKinds, KindSignatures, ScopedTypeVariables,
             AllowAmbiguousTypes, TypeApplications #-}

data N = Zero | Succ N

-- Now n is restricted to kind N
class Nat (n :: N) where
  toInt :: Int

instance Nat Zero where
  toInt = 0
instance (Nat n) => Nat (Succ n) where
  toInt = 1 + toInt @n

将此用作toInt @(Succ Zero)toInt @n语法在类型类中选择n。它不与运行时交换的任何值相对应,仅与在编译时存在的类型级参数相对应。


使用

type Foo = Succ Int

也根据需要出错:

    • Expected kind ‘N’, but ‘Int’ has kind ‘*’
    • In the first argument of ‘Succ’, namely ‘Int’
      In the type ‘Succ Int’
      In the type declaration for ‘Foo’