我不明白“类型级编程”是什么意思,也无法通过谷歌找到合适的解释。
请问有人能提供一个演示类型级编程的例子吗?对该范 Paradigm 的解释和/或定义也会很有用。
我不明白“类型级编程”是什么意思,也无法通过谷歌找到合适的解释。
请问有人能提供一个演示类型级编程的例子吗?对该范 Paradigm 的解释和/或定义也会很有用。
你已经熟悉“值级别”编程,通过它你可以操作如42 :: Int
或'a' :: Char
之类的值。在像Haskell、Scala等许多其他语言中,类型级别编程允许你操作像Int :: *
或Char :: *
这样的类型,其中*
是具体类型的类别(Maybe a
或[a]
是具体类型,但Maybe
或[]
不是,它们的类别为* -> *
)。
考虑这个函数
foo :: Char -> Int
foo x = fromEnum x
这里的foo
接受一个类型为Char的value,并使用Char
的Enum
实例返回一个新的Int
类型的值。该函数操作值。
现在将foo
与启用了TypeFamilies
语言扩展的这个类型族进行比较。
type family Foo (x :: *)
type instance Foo Char = Int
在这里,Foo
接受一种 kind 为 *
的 type 并使用简单的映射 Char -> Int
返回一个新的 *
类型。这是一个操作类型的类型级函数。
这只是一个非常简单的例子,你可能会想知道它如何可能有用。使用更强大的语言工具,我们可以开始在类型级别上对代码的正确性进行编码证明(更多信息请参见Curry-Howard correspondence)。
一个实际的例子是红黑树,它使用类型级编程来静态保证树的不变量。
红黑树具有以下简单属性:
我们将使用 DataKinds
和 GADTs
,这是一种非常强大的类型级编程组合。
{-# LANGUAGE DataKinds, GADTS, KindSignatures #-}
import GHC.TypeLits
首先,一些用于表示颜色的类型。
data Colour = Red | Black -- promoted to types via DataKinds
这定义了一个新类型Colour
,它包含两种类型:Red
和Black
。请注意,这些类型中没有任何实际的数值(忽略底部),但我们也不需要它们。
红黑树节点由以下GADT
表示:
-- 'c' is the Colour of the node, either Red or Black
-- 'n' is the number of black child nodes, a type level Natural number
-- 'a' is the type of the values this node contains
data Node (c :: Colour) (n :: Nat) a where
-- all leaves are black
Leaf :: Node Black 1 a
-- black nodes can have children of either colour
B :: Node l n a -> a -> Node r n a -> Node Black (n + 1) a
-- red nodes can only have black children
R :: Node Black n a -> a -> Node Black n a -> Node Red n a
GADT
让我们可以直接在类型中表达R
和B
构造函数的Colour
。
树的根看起来像这样
data RedBlackTree a where
RBTree :: Node Black n a -> RedBlackTree a
现在创建一个违反上述四条属性之一的类型化的RedBlackTree
是不可能的。
Colour
中。RedBlackTree
的定义,根节点是黑色的。Leaf
构造函数的定义,所有叶子都是黑色的。R
构造函数的定义,它的两个子节点都必须是Black
节点。同样,每个子树的黑色子节点数目相等(左右子树的类型中使用相同的n
)。GHC会在编译时检查所有这些条件,这意味着我们永远不会因为某些行为不当的代码而得到运行时异常,从而使我们对红黑树的假设无效。重要的是,这些额外的好处没有与之相关的运行时成本,所有工作都在编译时完成。
在大多数静态类型语言中,你有两个“领域”:值级别和类型级别(某些语言甚至有更多)。类型级别编程涉及在类型系统中编码逻辑(通常是函数抽象),这在编译时进行评估。一些例子包括模板元编程或 Haskell 类型家族。
为了在 Haskell 中完成这个示例,需要一些语言扩展,但现在你可以忽略它们,只需将类型家族视为在类型级别数字(Nat
)上的函数即可。
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
import GHC.TypeLits
import Data.Proxy
-- value-level
odd :: Integer -> Bool
odd 0 = False
odd 1 = True
odd n = odd (n-2)
-- type-level
type family Odd (n :: Nat) :: Bool where
Odd 0 = False
Odd 1 = True
Odd n = Odd (n - 2)
test1 = Proxy :: Proxy (Odd 10)
test2 = Proxy :: Proxy (Odd 11)
这里我们不是测试自然数value是否为奇数,而是测试自然数type是否为奇数,并将其在编译时降级为类型级别的布尔值。如果您评估此程序,则会在编译时计算test1
和test2
的类型:
λ: :type test1
test1 :: Proxy 'False
λ: :type test2
test2 :: Proxy 'True
这就是类型级编程的精髓,根据语言的不同,你可以在类型级别上编码复杂逻辑,其具有各种用途。例如限制值级别上的某些行为、管理资源终止或存储更多关于数据结构的信息。
UndecidableInstances
?这样的工具应该只在实际使用的模块中使用,是吧? - dfeuer