../
Type-level编程:一个最平凡的例子
================================

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