简体   繁体   English

在类型声明中键入限制

[英]Type restriction in type declaration

There is famous example of the type level natural numbers: 有一个着名的类型级自然数的例子:

data Zero
data Succ n

I have a question about desirable restriction when we applying type constructor Succ . 当我们应用类型构造函数Succ时,我有一个关于理想限制的问题。 For example, if we want to make such restriction in function definition, we can use the class and context, like in this code: 例如,如果我们想在函数定义中进行这样的限制,我们可以使用类和上下文,就像在这段代码中一样:

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)

It's impossible to use toInt (undefined :: Succ Int) , it's ok. 使用toInt (undefined :: Succ Int)是不可能的,没关系。

But how to realize similar restrictions on type level constructions (perhaps with some advanced type extensions)? 但是如何在类型级别构造上实现类似的限制(可能有一些高级类型扩展)?

For instance, I'd like to permit using of type constructor Succ only with type Zero and with something, like this: Succ (Succ Zero) , Succ (Succ (Succ Zero)) and so on. 例如,我想允许使用类型构造函数Succ只使用类型Zero和类似的东西: Succ (Succ Zero)Succ (Succ (Succ Zero))等等。 How to avoid such bad example on compile time: 如何在编译时避免这样的坏例子:

type Test = Succ Int

(for now, there is no compilation error) (目前,没有编译错误)

PS: It is more interesting for me how to create a restriction on the type declaration of the last sample PS:对我来说,如何对最后一个样本的类型声明创建限制更有意思

Nowadays, we use the DataKinds extension: 如今,我们使用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)

We can then use toInt (Proxy :: Proxy (Succ Zero)) . 然后我们可以使用toInt (Proxy :: Proxy (Succ Zero)) Instead, toInt (Proxy :: Proxy (Succ Int)) will raise a kind error, as wanted. 相反, toInt (Proxy :: Proxy (Succ Int))会根据需要引发类型错误。

Personally, I would also replace proxies with more modern stuff like AllowAmbiguousTypes and TypeApplications so to remove the unused argument. 就个人而言,我也将取代更现代的东西,如代理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

Use this as toInt @(Succ Zero) . 使用它作为toInt @(Succ Zero) The toInt @n syntax chooses the n in the typeclass. toInt @n语法选择类型类中的n It does not correspond to any value exchanged at runtime, only a type-level argument which exists at compile time. 它不对应于在运行时交换的任何值,只是在编译时存在的类型级参数。


Using 运用

type Foo = Succ Int

also errors out as wanted: 也想要错误:

    • 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’

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM