有一个著名的类型级自然数示例:
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 .:对我来说,更有趣的是如何对最后一个样本的类型声明创建限制
答案 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))
会根据需要引发一种错误。
就个人而言,我还可以将代理替换为更现代的东西,例如AllowAmbiguousTypes
和TypeApplications
,以便删除未使用的参数。
{-# 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’