3

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

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

(目前,没有编译错误)

PS:对我来说更有趣的是如何对最后一个样本的类型声明进行限制

4

1 回答 1

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’
于 2019-06-26T17:34:43.067 回答