类型声明中的类型限制

Vla*_*mir 3 haskell type-systems gadt phantom-types data-kinds

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

data Zero
data Succ n
Run Code Online (Sandbox Code Playgroud)

当我们应用类型构造函数时,我有一个关于理想限制的问题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)
Run Code Online (Sandbox Code Playgroud)

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

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

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

type Test = Succ Int
Run Code Online (Sandbox Code Playgroud)

(目前,没有编译错误)

PS:对我来说更有趣的是如何对最后一个示例的类型声明创建限制

chi*_*chi 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)
Run Code Online (Sandbox Code Playgroud)

然后我们可以使用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
Run Code Online (Sandbox Code Playgroud)

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


使用

type Foo = Succ Int
Run Code Online (Sandbox Code Playgroud)

也根据需要出错:

    • 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’
Run Code Online (Sandbox Code Playgroud)