../
类型级别编程:一个最平凡的例子
================================
2025-05-17
本文将使用Haskell。
起手先开一些扩展:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DataKinds#-}
{-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -Werror=incomplete-patterns #-}
import Data.Type.Equality
## 零一
计算机中的一切都是「零」和「一」:
data Bit = Zero | One
为Bit类型中的两个值:「零」和「一」,各自创建类型,是为单例类型(Singleton
Type):
-- 前缀S,是Singleton的缩写
data SBit :: Bit -> * where
SZero :: SBit 'Zero
SOne :: SBit 'One
## 与非门
一切布尔电路都可以用「与非门」完成,但是我们首先要定义「与非门」:
type family Nand (a :: Bit) (b :: Bit) :: Bit where
Nand 'One 'One = 'Zero
Nand _ _ = 'One
`type family`给出的是“定义”,也可以称为“标准”(Specification/Spec)。证明也好,
检查也好,都是要让程序必须符合这里的标准。
然后我们可以实现它:
nandGate :: SBit a -> SBit b -> SBit (Nand a b)
nandGate SOne SOne = SZero
nandGate SZero _ = SOne
nandGate _ SZero = SOne
## 非门
零变成一,一变成零,这就是非门。因此给出定义:
type family Not (a :: Bit) :: Bit where
Not 'One = 'Zero
Not 'Zero = 'One
「非门」可以利用「与非门」实现:
notGateImpl x = nandGate x x
把实现提交给编译器:
notGate :: SBit a -> SBit (Not a)
notGate = notGateImpl
被编译器驳回:
Couldn't match type: Nand a a
with: Not a
Expected: SBit a -> SBit (Not a)
Actual: SBit a -> SBit (Nand a a)
notGateImpl返回的是`SBit (Nand a a)`,而notGate的返回值所期望的是`SBit (Not a)`。
我们需要证明`SBit (Not a)`和`SBit (Nand a a)`是同一个东西。这样,就可以实现转化。
这里有两种思路,第一种是用纯粹的力量碾压过去,用暴力枚举证明这个东西就是绝对成
立的:
notGateProof :: SBit a -> SBit (Nand a a) :~: SBit (Not a)
notGateProof SOne = Refl
notGateProof SZero = Refl
然后利用这个证明实现安全的类型转换:
notGate :: SBit a -> SBit (Not a)
notGate x = castWith (notGateProof x) (notGateImpl x)
这样「非门」就完成了。
第二种则是退而求其次,只增加一个类型约束:
notGate :: (SBit (Nand a a) ~ SBit (Not a)) => SBit a -> SBit (Not a)
notGate x = nandGate x x
这样的也能通过编译,而且代码比较简单。
不过这样更像是一种“静态检查”。例如,我们故意写一个错误的实现:
notGate :: (SBit 'Zero ~ SBit (Not a)) => SBit a -> SBit (Not a)
notGate x = SZero
显然,`SBit 'Zero`和`SBit (Not a)`在`a`是`'Zero`的时候是不等价的,但是这里还是
可以通过编译。
如果程序中出现了`x`是SOne的`notGate`调用:
zero = notGate SOne
因为恰好满足条件,还是可以通过编译。
只有在程序中出现`x`是`SZero`的`notGate`调用时:
one = notGate SZero
才会在类型检查中发现问题,此时编译器会报错:
• Couldn't match type ‘'Zero’ with ‘'One’
arising from a use of ‘notGate’
• In the expression: notGate SZero
In an equation for ‘one’: one = notGate SZero
因为第二种比较简单,所以后面不再赘述,只介绍第一种。从实用的角度来看,第一种的
证明经常难以给出,大多数时候只需要第二种这样的“静态检查”就够了。
## 与门
何为「与门」?一一得一,余下皆零:
type family And (a :: Bit) (b :: Bit) :: Bit where
And 'One 'One = 'One
And _ _ = 'Zero
与非门加上非门,就是与门:
andGateImpl a b = notGate $ nandGate a b
用枚举法暴力求证:
andGateProof :: SBit a -> SBit b -> SBit (Not (Nand a b)) :~: SBit (And a b)
andGateProof SZero SOne = Refl
andGateProof SZero SZero = Refl
andGateProof SOne SOne = Refl
andGateProof SOne SZero = Refl
提交给编译器:
andGate :: SBit a -> SBit b -> SBit (And a b)
andGate a b = castWith (andGateProof a b) (andGateImpl a b)
「与门」完成了。
## 或门
照例先给出定义,零零得零,余下皆一:
type family Or (a :: Bit) (b :: Bit) :: Bit where
Or 'Zero 'Zero = 'Zero
Or _ _ = 'One
尝试一下花哨的实现方法,利用De Morgan定律,利用「与门」和「非门」组合出「或门」:
orGateImpl a b = notGate (andGate (notGate a) (notGate b))
继续暴力枚举证明自己是对的:
orGateProof :: SBit a -> SBit b
-> SBit(Not (And (Not a) (Not b))) :~: SBit (Or a b)
orGateProof SZero SZero = Refl
orGateProof SZero SOne = Refl
orGateProof SOne SZero = Refl
orGateProof SOne SOne = Refl
编译器欣然接受:
orGate :: SBit a -> SBit b -> SBit (Or a b)
orGate a b = castWith (orGateProof a b) (orGateImpl a b)
## 结尾
这里给出的是一个平凡到不能再平凡的例子,不过用到的语言特性还挺多的,算是对最近
学习的一个小结。但是如果要实现一些不平凡的例子的话,往往要用一些很复杂很丑的
Template Haskell库,不太想碰,所以准备后面去看看Lean 4去。
--------------------------------------------------------------------
Email: i (at) mistivia (dot) com