什么是类型级编程的例子?

时间:2014-06-29 23:18:35

标签: scala haskell functional-programming type-level-computation

我不明白“类型级编程”是什么意思,我也无法使用Google找到合适的解释。

有人可以提供演示类型级编程的示例吗?范式的解释和/或定义将是有用和赞赏的。

3 个答案:

答案 0 :(得分:30)

您已经熟悉"价值水平"编程,您可以操纵42 :: Int'a' :: Char等值。在Haskell,Scala等许多语言中,类型级编程允许您操作Int :: *Char :: *等类型,其中*的类型具体类型(Maybe a[a]是具体类型,但不是具有Maybe种类的[]* -> *

考虑这个功能

foo :: Char -> Int
foo x = fromEnum x

此处foo获取类型 Char,并使用{{1}返回类型为Int的新值} Enum的实例。此函数操纵值。

现在将Char与此类型系列进行比较,并使用foo语言扩展程序启用。

TypeFamilies

此处type family Foo (x :: *) type instance Foo Char = Int 使用 Foo类型并使用简单映射返回新类型的* { {1}}。这是一个操纵类型的类型级函数。

这是一个非常简单的例子,您可能想知道这可能是多么有用。使用更强大的语言工具,我们可以开始在类型级别编码代码正确性的证明(有关详细信息,请参阅Curry-Howard对应关系)。

一个实际的例子是一个红黑树,它使用类型级编程来静态地保证树的不变量保持不变。

红黑树具有以下简单属性:

  1. 节点为红色或黑色。
  2. 根是黑色的。
  3. 所有叶子都是黑色的。 (所有叶子的颜色与根相同。)
  4. 每个红色节点必须有两个黑色子节点。一切 从给定节点到其任何后代叶子的路径包含 相同数量的黑色节点。
  5. 我们将使用*Char -> Int,这是一种非常强大的类型级别编程组合。

    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构造函数的Colour

    树的根看起来像这样

    R

    现在不可能创建一个违反上述4个属性中任何一个的良好类型B

    1. 第一个约束显然是正确的,只有两种类型居住data RedBlackTree a where RBTree :: Node Black n a -> RedBlackTree a
    2. 根据RedBlackTree的定义,根是黑色的。
    3. 根据Colour构造函数的定义,所有叶子都是黑色的。
    4. RedBlackTree构造函数的定义来看,它的两个孩子都必须这样做 是Leaf个节点。同样,每个子树的黑色子节点数相等(在左右子树的类型中使用相同的R
    5. GHC在编译时检查所有这些条件,这意味着我们永远不会从一些行为不当的代码中获得运行时异常,从而使我们对红黑树的假设无效。重要的是,没有与这些额外好处相关的运行时成本,所有工作都在编译时完成。

答案 1 :(得分:23)

在大多数静态类型语言中,您有两个"域名"价值水平和类型水平(某些语言甚至更多)。类型级编程涉及在编译时评估的类型系统中的编码逻辑(通常是函数抽象)。一些例子是模板元编程或Haskell类型族。

在Haskell中执行此示例需要一些语言扩展,但您现在暂时忽略它们,只是将类型族视为函数,而不是类型级数(Nat)。 / p>

{-# 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)

此处不是测试自然数是否为奇数,而是测试自然数类型是否为奇数并将其减少为编译时的类型级布尔值。如果您评估此程序,test1test2的类型将在编译时计算为:

λ: :type test1
test1 :: Proxy 'False
λ: :type test2
test2 :: Proxy 'True

这是类型级编程的本质,取决于您可能能够在类型级别编码具有各种用途的复杂逻辑的语言。例如,限制值级别的某些行为,管理资源最终化,或存储有关数据结构的更多信息。

答案 2 :(得分:12)

其他答案非常好,但我想强调一点。我们的术语的编程语言理论主要基于Lambda微积分。 A"纯粹" Lisp对应于(或多或少)严重加糖的无类型Lambda微积分。程序的含义由评估规则定义,该规则说明在程序运行时如何减少Lambda微积分项。

在键入的语言中,我们为术语分配类型。对于每个评估规则,我们都有一个相应的类型规则,显示如何通过评估保留类型。根据类型系统,还有其他规则定义类型彼此之间的关系。事实证明,一旦你获得了一个足够有趣的类型系统,类型及其规则系统也对应于Lambda微积分的变体!

虽然现在将Lambda Calculus视为一种编程语言很常见,但最初它被设计为逻辑系统。这就是为什么它可以用于推理编程语言中的术语类型。但Lambda Calculus的编程语言方面允许人们编写由类型检查器评估的程序。

希望您现在可以看到"类型级编程"与术语级编程并不是完全不同的事情,现在在类型系统中拥有足够强大的语言并不常见您有理由在其中编写程序。