强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

10 类型系统

10 类型系统

“类型系统是程序员的契约——它在编译时检查程序的正确性,让你在运行前就发现错误。”


10.1 为什么函数式编程重视类型

函数式编程与强类型系统天然契合。类型系统能够在编译时捕获大量错误,同时也是程序设计的文档。

10.1.1 类型系统分类

分类维度类型代表语言
静态 vs 动态静态类型在编译时检查Haskell, Rust (静态); Python, JS (动态)
强 vs 弱强类型不允许隐式转换Haskell, Rust (强); JS (弱)
显式 vs 推断类型推断无需手写类型Haskell, Rust (推断); Java (显式)
名义 vs 结构结构类型基于形状Haskell (名义); TypeScript (结构)

10.1.2 类型系统的好处

好处说明
早期错误检测编译时发现类型不匹配
程序文档类型签名即接口文档
重构安全编译器帮助找出所有需要修改的地方
优化提示编译器根据类型信息进行优化
IDE 支持自动补全、跳转定义等

10.2 Hindley-Milner 类型系统

Hindley-Milner (HM) 类型系统是 Haskell 和 ML 语言族的基础,它支持强大的类型推断。

10.2.1 类型推断

类型推断(Type Inference)让编译器自动推导表达式的类型,无需显式标注。

-- 编译器可以推断出所有类型
id x = x            -- id :: a -> a
const x y = x       -- const :: a -> b -> a
add x y = x + y     -- add :: Num a => a -> a -> a

-- 类型推断过程
-- x = 5         → x :: Int(从字面量推断)
-- y = x + 1     → y :: Int(从 x + 1 推断)
-- f = \x -> x+1 → f :: Int -> Int

-- 推断算法(简化版)
-- 1. 给每个表达式分配类型变量 (a, b, c, ...)
-- 2. 收集约束(unification constraints)
-- 3. 统一求解(unification)
-- 4. 泛化(generalization)→ 多态类型

Rust 的类型推断:

let x = 5;           // x: i32
let y = x + 1;       // y: i32
let z = vec![1, 2];  // z: Vec<i32>

// 闭包类型推断
let add = |a, b| a + b;  // add: impl Fn(i32, i32) -> i32

// 泛型函数类型推断
fn identity<T>(x: T) -> T { x }
let val = identity(42);  // val: i32

10.2.2 多态(Polymorphism)

HM 系统支持参数多态(Parametric Polymorphism)。

-- 参数多态:类型参数
identity :: a -> a
identity x = x

-- 可以用于任何类型
identity 42          :: Int
identity "hello"     :: String
identity [1, 2, 3]   :: [Int]

-- 多参数多态
const :: a -> b -> a
const x y = x

compose :: (b -> c) -> (a -> b) -> a -> c
compose f g x = f (g x)

-- 自然变换(Natural Transformation):不关心具体类型
-- ∀ f. Functor f => f a -> f a

TypeScript:

// 泛型函数
function identity<T>(x: T): T {
  return x;
}

identity(42);       // 推断为 number
identity("hello");  // 推断为 string

// 约束泛型
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

// 条件类型
type IsString<T> = T extends string ? true : false;

10.3 类型类(Type Classes)

类型类是 Haskell 特有的特现,它提供了一种轻量级的多态机制。

10.3.1 定义与使用

-- 定义类型类
class Printable a where
  toString :: a -> String

-- 为类型实现实例
instance Printable Int where
  toString = show

instance Printable Bool where
  toString True  = "Yes"
  toString False = "No"

instance Printable a => Printable [a] where
  toString xs = "[" ++ intercalate ", " (map toString xs) ++ "]"

-- 使用
printValue :: Printable a => a -> IO ()
printValue x = putStrLn (toString x)

-- 常见类型类
-- Eq      (==)  相等比较
-- Ord     compare 排序
-- Show    show  转字符串
-- Read    read  字符串转类型
-- Num     (+), (*), abs 数值运算
-- Functor fmap  函子
-- Monad   >>=   单子

10.3.2 类型类 vs 接口

特性Haskell 类型类Java/TypeScript 接口
定义时机可以为已有类型添加必须在类型定义时实现
多实现同一类型可有多个实例一个类可实现多个接口
带类型参数支持(如 Functor f有限支持
自动推导deriving 自动生成需要手动实现

10.4 代数数据类型的类型表示

10.4.1 类型构造器

-- 类型构造器
data Maybe a = Nothing | Just a
-- Maybe :: * -> *   (接受一个类型参数)

data Either a b = Left a | Right b
-- Either :: * -> * -> *   (接受两个类型参数)

data Pair a b = Pair a b
-- Pair :: * -> * -> *

-- 高阶类型
data Fix f = Fix (f (Fix f))
-- Fix :: (* -> *) -> *

10.4.2 类型级别编程

-- 类型族(Type Families)
type family Element a where
  Element [a]    = a
  Element (Set a) = a
  Element (Map k v) = (k, v)

-- GADTs(广义代数数据类型)
data Expr a where
  LitI  :: Int -> Expr Int
  LitB  :: Bool -> Expr Bool
  Add   :: Expr Int -> Expr Int -> Expr Int
  If    :: Expr Bool -> Expr a -> Expr a -> Expr a

eval :: Expr a -> a
eval (LitI n)    = n
eval (LitB b)    = b
eval (Add a b)   = eval a + eval b
eval (If c t e)  = if eval c then eval t else eval e

10.5 高阶类型(Higher-Kinded Types)

10.5.1 Kind 系统

Kind 是"类型的类型",描述类型构造器的结构。

-- Kind 表示法
Int       :: *              -- 具体类型
Maybe     :: * -> *         -- 接受一个类型参数
Either    :: * -> * -> *    -- 接受两个类型参数
Monad     :: (* -> *) -> Constraint  -- 接受一个类型构造器

-- Kind 签名的类型类
class Functor f where
  fmap :: (a -> b) -> f a -> f b
  -- f 的 kind 是 * -> *

class Functor f => Applicative f where
  pure  :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b

class Applicative m => Monad m where
  (>>=) :: m a -> (a -> m b) -> m b

10.5.2 模拟高阶类型

// TypeScript 模拟 HKT(使用编码技巧)
interface HKT<F, A> {
  _F: F;
  _A: A;
}

// 注册类型
interface HKTMap {
  Maybe: Maybe<any>;
  Array: Array<any>;
}

// 使用
type Apply<F extends keyof HKTMap, A> = HKTMap[F] extends HKT<F, A>
  ? HKT<F, A>
  : never;

Rust(使用 GAT - Generic Associated Types):

trait Functor {
    type Wrapped<A>;

    fn map<A, B, F>(fa: Self::Wrapped<A>, f: F) -> Self::Wrapped<B>
    where
        F: Fn(A) -> B;
}

struct OptionFunctor;

impl Functor for OptionFunctor {
    type Wrapped<A> = Option<A>;

    fn map<A, B, F>(fa: Option<A>, f: F) -> Option<B>
    where
        F: Fn(A) -> B,
    {
        fa.map(f)
    }
}

10.6 依赖类型(Dependent Types)

依赖类型允许类型依赖于值。

-- Idris(依赖类型语言)示例
-- 向量长度在类型中编码
data Vect : Nat -> Type -> Type where
  Nil  : Vect 0 a
  (::) : a -> Vect n a -> Vect (S n) a

-- 类型保证长度正确
append : Vect n a -> Vect m a -> Vect (n + m) a
append Nil       ys = ys
append (x :: xs) ys = x :: append xs ys

-- head 不可能对空列表调用(编译时保证)
head : Vect (S n) a -> a
head (x :: _) = x

10.7 实用类型技巧

10.7.1 新类型模式(Newtype Pattern)

-- 用 newtype 创建类型安全的别名
newtype UserId = UserId Int
newtype Email = Email String

-- 防止类型混淆
sendEmail :: Email -> Subject -> Body -> IO ()
sendEmail (Email addr) subj body = ...

-- 不能传 UserId 给 sendEmail
// Rust 的 newtype
struct UserId(u64);
struct Email(String);

fn send_email(to: &Email, subject: &str, body: &str) { ... }

// 不能传 UserId 给 send_email

10.7.2 Phantom Type(幽灵类型)

-- 幽灵类型参数不出现在值构造器中
data Validated
data Unvalidated

data User a = User { name :: String, email :: String }

validate :: User Unvalidated -> Maybe (User Validated)
validate (User n e)
  | '@' `elem` e && not (null n) = Just (User n e)
  | otherwise = Nothing

-- 只有验证过的用户才能注册
register :: User Validated -> IO ()
register user = ...

10.8 业务场景

10.8.1 类型安全的 API 客户端

// TypeScript 类型安全的 API 客户端
type ApiResponse<T> =
  | { status: 'success'; data: T }
  | { status: 'error'; message: string };

interface User { id: number; name: string; email: string; }
interface Post { id: number; title: string; body: string; userId: number; }

// 类型安全的 fetch
async function apiGet<T>(url: string): Promise<ApiResponse<T>> {
  try {
    const response = await fetch(url);
    if (!response.ok) return { status: 'error', message: response.statusText };
    const data = await response.json();
    return { status: 'success', data: data as T };
  } catch (err) {
    return { status: 'error', message: String(err) };
  }
}

// 使用(类型安全)
const userResult = await apiGet<User>('/api/users/1');
const postResult = await apiGet<Post[]>('/api/posts');

// 处理结果
if (userResult.status === 'success') {
  console.log(userResult.data.name);  // 类型安全
}

10.9 注意事项

注意事项说明
类型注解适时添加类型注解帮助理解和编译器
过度抽象不要为了类型安全而过度复杂化
错误信息复杂类型的错误信息难以理解
泛型约束使用约束限制泛型范围
any/void避免过度使用 any,它破坏类型安全

10.10 小结

要点说明
类型推断编译器自动推导类型,减少手写类型注解
Hindley-MilnerHaskell 的类型系统基础,支持参数多态
类型类轻量级多态,可为已有类型添加接口
高阶类型类型构造器的多态,Functor/Monad 的基础
依赖类型类型依赖值,提供更强的正确性保证
Newtype类型安全的别名,防止类型混淆

扩展阅读

  1. Type Classes - Haskell Wiki
  2. Types and Programming Languages — Benjamin C. Pierce
  3. Higher-Kinded Types in Rust
  4. TypeScript 类型体操 — Type Challenges

下一章11 范畴论入门 — 函数式编程的数学基础