# 理解编程的更多方式

## 除了面向过程和面向对象，现在发展到哪了

Last Updated: 2026-05-09

## 摘要

学校里的编程语言入门，常常从面向过程和面向对象开始，最多再补一句函数式编程。但继续往后看，会发现编程语言和程序系统的发展远不止这些分类。AST、类型系统、IR、Effect、Actor、KPN、BOC 这些概念来自不同领域，却都在做相似的事：把原本靠程序员经验维持的结构显化出来，让系统能够理解、检查、调度、优化和恢复。

这篇文章不是完整的 PL 综述，而是一条个人学习路径：从学校里的语言分类，到编译器里的结构表示，再到并发模型、数据流、失败恢复和 AI Agent 时代的重新相遇。它想回答的问题是：除了面向过程和面向对象，程序还能被理解成什么？

## 学校里的那张小地图

### 入门分类

我上学时接触编程语言，最常见的分类是面向过程和面向对象。面向过程大概对应 C 这种语言：程序是一串步骤，函数把步骤分块，变量保存中间状态。面向对象则对应 C++、Java 这类语言：程序被组织成对象，对象有状态，也有方法，程序运行时就是对象之间互相调用。

有些教材会在后面再补一句：除了面向过程和面向对象，还有函数式编程。但通常也只是补一句。于是很多人早期形成的语言地图，大概就是过程、对象、函数这几块。

这张地图没有错。它对入门很有帮助，也能解释很多常见语言的差异。但后来继续往下学，会发现它太小了。编程语言和程序系统的发展，并不只是“过程 vs 对象”，也不只是多一个“函数式”标签。很多重要变化发生在更深的一层：程序中原本靠程序员经验、约定和手工控制维持的结构，逐渐被语言、框架和 runtime 表达出来，变成系统可以理解、检查、调度、优化和恢复的对象。

### “显化”这个关键词

我这里想用一个词概括这个过程：**显化**。

显化不是简单地“写得更明确”。它指的是：原本隐含在程序员脑子里的东西，被提升成语言或系统的一部分。比如，类型系统把“这个值应该是什么形状”显化出来；dataflow 把“谁依赖谁”显化出来；actor 把“状态归谁管理”显化出来；checkpoint 把“失败后从哪里恢复”显化出来。

### 我的学习路径

我对这件事的理解，也是慢慢建立起来的。上学时用 C++ 和 LLVM 写过一个 COOL 语言的编译器，第一次知道源代码不是直接变成机器码，中间还有 AST、类型检查和 IR。后来学大数据和 Flink，才意识到程序不一定是顺序执行，也可以被看成数据流图。最近再看 BOC、FP、PL、Agent runtime，又发现并发、资源、失败、恢复、副作用这些东西，都可以被设计成新的程序模型。

所以这篇文章不是想重新给编程语言做完整分类，而是想记录一条个人学习路径：编程语言除了面向过程和面向对象，后来到底还在发展什么？

我的答案是：它一直在把各种隐含结构显化。

## 第一层显化：程序不只是指令流

### 从机器指令开始

如果从机器的角度看程序，最直接的模型是指令流。汇编语言很接近这个模型：一条条 instruction，操作寄存器，读写内存，通过 label 和 jump 改变控制流。程序当然可以有子程序和调用约定，但从机器视角看，它主要还是线性指令加跳转。

C 比汇编高级很多。它有函数、变量、结构体、if、while、for。程序员不用直接写寄存器和跳转，也不用每次都手动管理底层指令。但 C 的心智模型仍然很接近顺序机器：程序一步步执行，变量对应内存里的值，指针可以直接触碰地址，函数调用会压栈和返回。

高级语言真正带来的变化，不只是语法更舒服，而是程序开始有了更高层的结构。

### AST：语法结构

比如一段 `if` 语句，在文本里只是字符序列；在机器码里可能是比较、跳转和基本块；但在编译器眼里，它首先会被解析成一棵 AST。AST 把源代码中的嵌套结构显化出来：条件是什么，then 分支是什么，else 分支是什么，表达式如何组合，函数定义在哪里，变量声明属于哪个作用域。

这就是第一层变化：程序不再只是文本，也不只是指令流，而是树状结构。

### Type System 与 IR

接着是类型系统。汇编里没有高级类型，只有字节、寄存器、地址，以及指令如何解释这些位。高级语言引入类型以后，程序多了一层约束：这个变量是整数，那个函数接收字符串，这个对象有某些字段，这个表达式的返回值必须符合某种形状。

类型系统显化的是合法性约束。它让一部分错误可以在运行前被发现，而不是等程序跑到某一行才崩。

再往后是 IR。AST 很适合表达“程序员怎么写”，但不一定适合优化和生成机器码。编译器通常会把 AST 转成更适合分析和变换的中间表示。比如 LLVM IR、SSA 形式、控制流图、数据流信息，都在帮助编译器理解程序如何流动、值从哪里来、哪些操作可以优化。

## 第二层显化：组合不只是函数调用

### 函数调用的边界

过程式语言里，最自然的组合方式是函数调用。A 调 B，B 调 C。把重复逻辑抽成函数，把复杂流程拆成多个函数，然后通过调用关系组合起来。

这个模型非常基础，也非常强大。大多数工程代码都是这么组织的。但当系统复杂以后，函数调用本身也会暴露出问题。

一个函数表面上可能是：

```text
A -> B
```

但真实情况经常不是这么简单。它可能读文件，写数据库，发网络请求，修改全局状态，抛异常，依赖上下文，或者在失败时需要重试。也就是说，函数调用背后经常藏着状态、失败、副作用和环境。

### 函数式编程的重新建模

函数式编程对这件事有另一种看法。它不只是“换一种语法写代码”，而是重新审视计算如何组合。纯函数的理想模型是：同样输入，总是得到同样输出，并且不偷偷修改外部世界。这样一来，函数就更容易组合、测试和推理。

高阶函数进一步把函数本身变成可以传递和组合的单元。`map`、`filter`、`reduce` 这些概念，本质上是在告诉程序员：你不一定要手写每一步循环，可以把“对每个元素做什么”作为一个可组合的计算传进去。

### Effect：副作用进入结构

再往后，组合会遇到更复杂的问题。如果一个步骤可能失败，或者会访问外部世界，它就不再是简单的 `A -> B`，而更像：

```text
A -> Effect[B]
```

这里的 `Effect` 可以代表很多东西：可能失败，可能异步，可能需要环境，可能访问文件、网络、数据库，也可能带着日志、上下文或取消语义。

Scala FP 生态里的 ZIO 是一个典型例子。它把一个 effectful program 表达成：

```text
ZIO[R, E, A]
```

这里的 `R` 表示这个计算需要什么环境或依赖，`E` 表示它可能失败成什么错误，`A` 表示成功时产生什么结果。

Applicative、Monad、Effect 这些词听起来很抽象，但从工程角度看，它们在回答一个问题：当步骤不再是普通函数，而是带着上下文、失败和副作用的计算时，它们还能不能被组合？如果能，组合规则是什么？

这就是第二层显化：组合不只是函数调用。调用背后的上下文、失败、副作用，也需要被表达出来。

函数式编程显化了计算组合。Effect 系统显化了副作用边界。Monad / Applicative 这类抽象显化了带上下文计算的组合规则。

所以函数式编程并不是学校教材里“除了过程和对象，还有一种写法”那么简单。它背后关心的是：如何让程序更可组合、更可推理，如何把隐藏在调用链里的状态和副作用显化出来。

## 第三层显化：程序不一定顺序执行

### 顺序模型的边界

过程式程序默认是顺序执行：

```text
A -> B -> C
```

程序员写出精确顺序，机器按照顺序执行。这个模型简单、直观，也很接近早期单机程序的运行方式。

但现实系统很快就会遇到顺序执行不够用的情况。服务器要同时处理多个请求，后台任务要并行运行，大数据系统要持续消费流，UI 要响应用户事件，分布式系统里多个节点要协作。程序不一定只有一条执行线。

### 线程和锁：最直接的答案

最直接的并发模型是线程和锁。多个线程共享内存，用锁保护共享资源。这个模型非常通用，也非常底层。它能解决并发执行的问题，但把大量正确性压力交给程序员：什么时候加锁，锁粒度多大，会不会死锁，会不会竞态，内存可见性如何保证。

上学期间学并发编程，很大程度上就是在学这些原语：线程、锁、条件变量、信号量、临界区、死锁、竞态。它们很重要，因为它们是很多系统的底层基础。但如果只停在这一层，就会觉得并发的核心问题都是“怎么正确用锁”。后来再看 Actor、CSP、Dataflow、BOC，才会发现还有另一条路：不是让程序员手工维护所有并发约束，而是把其中一部分约束提升成模型。

后来出现了很多并发模型，本质上是在把不同的并发约束显化出来。

### Actor 与 CSP：状态和通信

Actor 模型的思路是：不要让大家随便共享状态。每个 actor 管理自己的状态，外部通过消息和它交互。这样状态所有权被显化了。你不再需要到处想“这个变量会不会被另一个线程同时改”，因为状态属于某个 actor，消息进入它的 mailbox，由它自己处理。

CSP 强调的是通信。并发实体通过 channel 交换数据，程序结构围绕通信过程组织。相比共享内存加锁，它把“谁和谁通信、通过什么通道通信”显化出来。Go 的 channel 就有这种味道。

### Dataflow / KPN：依赖和调度

Dataflow / KPN 又是另一种视角。它不强调“先执行哪一行”，而强调“哪个节点依赖哪些数据”。当输入数据 ready，节点就可以执行。程序不再只是步骤列表，而是依赖图。KPN 里，process 通过 FIFO channel 通信；dataflow 系统里，operator 形成图，数据沿着边流动。

Flink 是这个方向的工程化例子。它不是一门 PL 语言，但它把 dataflow、state、checkpoint、backpressure 等执行语义做成了工业系统。在 Flink 里，程序员写的不是“第一秒做什么、第二秒做什么”，而是定义一个持续运行的数据流拓扑。runtime 负责调度、并行、状态管理和恢复。

### BOC：资源占用意图

BOC 则显化了另一种东西：资源占用意图。它不是让程序员手写锁，而是让 behavior 声明自己需要哪些 Cown。runtime 根据资源冲突来调度行为。这样一来，“这段计算需要独占哪些资源”不再藏在锁操作里，而是成为 runtime 可以理解的结构。

从线程锁到 Actor、CSP、Dataflow、BOC，变化的不是并发方式变多了，而是并发中的状态、通信、依赖和资源占用逐渐被显化。

这也是我在看 Flink 和 BOC 时感觉很多系统联系起来的原因。它们表面属于不同领域：一个是大数据流处理，一个是并发模型研究。但它们都在做同一件事：把顺序程序里靠人脑维护的调度意图，交给更明确的程序模型和 runtime。

## 第四层显化：失败和恢复不该散落在 if / try 里

### 局部错误处理的边界

普通程序里，失败处理通常是局部的。函数返回错误码，调用者判断一下；或者抛异常，上层 try / except；失败了 retry，最后 cleanup。

这种方式在短函数和小程序里够用。但系统一旦变成长流程、分布式系统、流处理系统，失败就不再是局部问题。

一个任务可能运行很久，中途调用多个外部系统，持有状态，产生副作用。失败以后，真正的问题不是“catch 一下异常”，而是：已经完成的步骤算不算数？哪些动作可以重放？哪些动作不能重放？状态恢复到哪里？如果外部系统已经被调用，是否需要补偿？

### 从 supervision 到 durable execution

Erlang 的 supervision tree 给出了一种思路：失败不是意外，而是系统设计的一部分。进程可以崩，监督者负责按策略重启。这里显化的是失败管理结构：谁监督谁，失败后怎么处理，不再散落在每个业务函数里。

Flink 的 checkpoint 解决的是流处理里的状态恢复。流处理任务持续运行，状态不断变化。如果某个节点失败，系统需要从一致的 checkpoint 恢复，而不是从头乱跑一遍。checkpoint 显化的是恢复点。

Temporal（一个持久工作流 / 持久执行系统）这类 durable execution 系统则把长流程的 retry、timeout、replay、补偿变成 runtime 语义。程序员描述 workflow，runtime 记录历史，失败后可以重放到安全位置继续执行。

这些系统关心的细节不同，但背后的方向一致：失败和恢复不应该只是散落在 if、try、retry、rollback 里的补丁。

第四层显化的是失败路径。失败不再只是异常分支，而是程序模型和 runtime 语义的一部分。

## 第五层显化：变化和优化也可以被建模

### 手工优化的边界

优化最开始常常是手工技巧。这个结果能不能缓存？这段逻辑能不能跳过？多个任务能不能并行？某些结果能不能复用？这些判断往往依赖程序员对业务逻辑的理解。

问题在于，当系统变大以后，“什么变了”和“谁依赖它”会变得很难维护。依赖关系藏在调用链、配置、文件、状态和数据库里。程序员想优化，就必须先在脑子里恢复出一张隐形依赖图。

### 依赖、身份和变化

编译器优化的前提，是程序已经被转成可分析结构。只有当编译器知道控制流、数据流、值的依赖关系，才可能做常量折叠、死代码消除、内联、循环优化等变换。

Build system 也是类似问题。Make、Bazel 这类系统关心的是：哪些文件依赖哪些文件，哪些目标依赖哪些目标。如果输入没有变化，就不用重建。这里显化的是构建依赖。

前端里的 React reconciliation 也是一个很好的例子。列表渲染时，如果没有 key，系统很难判断哪个元素和之前的哪个元素是同一个。key 把身份显化出来，系统才能决定哪些组件可以复用，哪些需要重新创建。

Incremental computation 更进一步：它关心的不是每次重新算一遍，而是只重算受影响的部分。Differential dataflow 则把变化本身作为一等对象传播，而不是反复处理全量状态。

这些方向看起来分散：编译器、构建系统、前端框架、流处理。但它们都在处理同一个问题：如何显化变化、依赖和复用关系。

当系统知道什么变了、谁依赖它、哪些结果仍然有效，就能自动决定什么需要重算，什么可以跳过，什么可以复用。

第五层显化的是变化本身。优化不再只是程序员手写技巧，而是系统理解程序结构之后自动做出的决策。

## 补充：正确性也可以被显化

### 从测试到规则和证明

前面几层讨论的是结构、组合、并发、恢复和优化。还有一个更进一步的方向：正确性。

通常我们依赖测试、经验和 code review 来建立信心。但测试只能覆盖有限情况，经验也可能出错。于是 PL 里有一些方向尝试把规则、约束和证明也变成机器可检查的结构。

Logic programming 的思路是：不写一步步执行的指令，而是写规则和约束。Prolog 是经典代表。Datalog 则可以用于查询、权限、静态分析等场景。它们显化的是规则和推理关系。

Proof assistant 和 dependent type 方向走得更远。它们尝试把程序和证明联系起来，让某些性质不只是“测试过应该没问题”，而是可以被形式化表达和检查。

这部分我现在还不适合展开太多。它更像是这条线的补充：如果类型系统显化的是“哪些程序合法”，那么逻辑和证明方向进一步尝试显化“为什么这个程序是对的”。

## AI 时代的小尾巴

### Agent 问题可以回到历史中找答案

这篇文章的主线不是 AI。但 AI 时代让这些老问题重新变得显眼。

Agent 程序里，代码、计划、工具调用和中间决策都可能由模型参与生成。于是系统面对的不再只是普通 bug，还有格式错误、参数错误、语义错误、幻觉、不可重放副作用和资源冲突。

这些问题并不是完全无根的，很多都能从历史里找答案：

- 输出不稳定：回到类型系统和 schema；
- 长程 Agent 对话上下文：回到状态和 Effect；
- workflow 需要并行和恢复：回到 dataflow、KPN 和 durable execution；
- 资源冲突和长期状态：回到 Actor、BOC 或其他并发模型。

所以 AI 时代不是突然出现一批完全陌生的问题，而是把很多程序系统里的经典问题重新组合在了一起。

回看 PL 和程序系统历史，能帮助我们判断今天的 Agent 框架到底在解决什么问题，哪些能力只是包装，哪些能力真的触及了运行时语义。

## 面向过程和面向对象只是入口

回到最开始的问题：编程语言除了面向过程和面向对象，现在发展到哪了？

我的理解是，面向过程和面向对象没有错，它们只是编程语言地图上的一角。继续往后看，会发现程序结构、合法性、组合、副作用、并发、失败、变化、正确性，都可以成为语言、框架和 runtime 的一部分。

这些概念不是为了炫技，也不是抽象越多越好。它们出现，是因为系统复杂到一定程度以后，某些责任不应该继续只靠程序员脑子硬扛。

学无止境，不是说要追更多新名词，而是要不断扩展自己理解程序的方式。

程序可以是指令流，可以是对象网络，可以是函数组合，可以是数据流图，可以是 actor 之间的消息，可以是 effect 描述，可以是可恢复 workflow，也可以是带证明的结构。

学到后面会发现，很多新的语言、框架和 runtime，其实都在反复做同一件事：把复杂性从人的经验里拿出来，变成系统可以理解和处理的结构。

## 参考资料

这些资料是后续扩写时可以回看的入口。部分是概念源头，部分是工程实现或现代框架示例。

### 语言谱系与编译器

- [Computer Languages History](https://levenez.com/lang/) (accessed: 2026-05-09)
- [Seven Languages in Seven Weeks](https://pragprog.com/titles/btlang/seven-languages-in-seven-weeks/) (accessed: 2026-05-09)
- [Seven More Languages in Seven Weeks](https://pragprog.com/titles/7lang/seven-more-languages-in-seven-weeks/) (accessed: 2026-05-09)
- [Stanford CS143: Compilers](https://web.stanford.edu/class/cs143/) (accessed: 2026-05-09)
- [The Cool Reference Manual](https://web.stanford.edu/class/cs143/materials/cool-manual.pdf) (accessed: 2026-05-09)
- [LLVM Language Reference Manual](https://llvm.org/docs/LangRef.html) (accessed: 2026-05-09)

### Effect 与函数式编程

- [ZIO Core Reference](https://zio.dev/reference/core/zio/) (accessed: 2026-05-09)
- [Cats Effect Docs](https://typelevel.org/cats-effect/docs/getting-started) (accessed: 2026-05-09)

### 并发、数据流与调度模型

- [Pekko Typed Actors](https://pekko.apache.org/docs/pekko/current/typed/index.html) (accessed: 2026-05-09)
- [Erlang/OTP](https://www.erlang.org/) (accessed: 2026-05-09)
- [Communicating Sequential Processes](https://cacm.acm.org/research/communicating-sequential-processes/) (accessed: 2026-05-09)
- [Ptolemy II Process Networks Domain](https://ptolemy.berkeley.edu/ptolemyII/ptII11.0/ptII/ptolemy/domains/pn/doc/main.htm) (accessed: 2026-05-09)
- [BOC: When Concurrency Matters](https://www.microsoft.com/en-us/research/publication/when-concurrency-matters-behaviour-oriented-concurrency/) (accessed: 2026-05-09)
- [Apache Flink Fault Tolerance](https://nightlies.apache.org/flink/flink-docs-stable/docs/learn-flink/fault_tolerance/) (accessed: 2026-05-09)

### 失败恢复与持久执行

- [Temporal Docs](https://docs.temporal.io/) (accessed: 2026-05-09)

### 变化、增量与复用

- [React Reconciliation](https://legacy.reactjs.org/docs/reconciliation.html) (accessed: 2026-05-09)
- [Salsa: A Generic Framework for On-Demand, Incrementalized Computation](https://salsa-rs.github.io/salsa/) (accessed: 2026-05-09)
- [Frank McSherry's Differential Dataflow Blog](https://github.com/frankmcsherry/blog) (accessed: 2026-05-09)
- [Materialize Docs](https://materialize.com/docs/) (accessed: 2026-05-09)

### 正确性与证明方向

- [Rocq Prover, formerly Coq](https://rocq-prover.org/) (accessed: 2026-05-09)
- [Rocq Documentation](https://rocq-prover.org/docs) (accessed: 2026-05-09)

### AI / Agent 交叉

- [Anthropic: Building Effective Agents](https://www.anthropic.com/engineering/building-effective-agents) (accessed: 2026-05-09)
- [OpenAI Agents SDK Docs](https://openai.github.io/openai-agents-python/) (accessed: 2026-05-09)
- [LangGraph Persistence](https://docs.langchain.com/oss/python/langgraph/persistence) (accessed: 2026-05-09)
- [PAgE 2026: Principles of Agentic Engineering](https://pldi26.sigplan.org/home/page-2026) (accessed: 2026-05-09)
