我们的软件构建了智能体可以彼此互动的数字空间. 使用机器学习算法来构建智能机器人是一个热门的话题. 但是大多数的代码仍然是用来构建这个虚拟环境自身的. 代码和运行时描述了在这个环境中什么可能发生, 什么将会发生, 也就是它的 "未来". 为了解决起义, 代码和运行时必须能够回答什么是 "现在" 正在发生的事情. 代码和运行时还必须忠实地记录 "过去" 发生了什么. 为了保持完整性以及打下坚实基础, 这三件事情都必须办得妥当, 否则产生了异常现象就是 bug. 最显而易见的数字空间就是如题图所示的在线 RPG 多人游戏.
当前商业兴趣正快速转移到构建更多的智能机器人上, 构建数字空间的技术业已白菜化, 一些过去的智慧可能会被丢失和遗忘. 当前主流的搭平台的做法行之有效而且成熟, 但是还谈不上优雅. 这个由三篇构成的系列尝试总结我在如何表达 "过去","现在" 和 "将来" 方面的观察. 其中的一些做法值得更多的关注.
用啥表达 "未来"? 这看起来是显而易见的问题. 那些所谓的业务逻辑由那么那么多的方式来表达. 我们以 "表达未来的方式" 的视角重新审视很多初级的概念. 然后我们可以看到这个问题其实没有我们想得那么简单.
源代码
"未来" 体现在了文档里, 在源代码里, 以及在运行时中. 我们将要使用的语言是 lua 5.3 和 es2017(ECMAScript 2017). 所有的代码可以在线尝试 https://tio.run/ .
简单过程
我们来看一个非常简单的过程: result = (a+b)*3. 这个 "some_important_business_process" 描述了接收 a 和 b 然后继续出结果的过程.
Function
最简单的描述 "什么可以发生以及什么将要发生" 的办法就是使用 Function.
lua version:
es2017 version: tio.run/
Partial Function
在前面的函数里, 我们一次性把 a 和 b 拿过来做为输入. 然而, 我们可能不是从一开始就知道 a 和 b 都是什么的. 为了把故事逐步展开, 我们先拿 "a", 然后再拿 "b".
lua version: tio.run/
es2017 version: tio.run/
变量 "proc" 把 "a" 捕获到了其中. 它是一个 continuation, 后续的执行可以从它那继续执行.
Object
有一个最初始的输入和一个最终的输出是不足以描述需要应对的情况的. 对未来的完整表达必须描述在将来获得更多的信息时如何处理. 为了实现这个目的, 一般的选择是使用 object 来存储过程, 然后定义一堆等着在事态发展之后被调用的 method.
lua version: tio.run/
es2017 version: tio.run/
这里的变量 "proc" 和之前的 "proc" 是一样的. 它是一个 continuation, 将来的执行会从这里继续.
State machine
然而, 使用对象并没有严格表达这个流程. 因为 step1 和 step2 是对等的. 没有地方定义了 step1 必须在 step2 之前执行. 更精确的表达方式是 state machine:
lua version: tio.run/
es2017 version: tio.run/
其中的 "next_step" 是一个游标, 它跟踪了执行的位置. 在 x86-64 CPU 中, 也有一个 instruction pointer register 名叫 "RIP", 干的活和 "next_step" 是类似的.
Coroutine
Coroutine 运作机制类似于 state machine, 但是写起来和 function 类似. 表达同样的概念用的代码要精简许多. 有两种风格的 coroutine, 一种是用 "yield"(也被称为 generator), 一种是用 "async/await". 我们先来看看 "yield" 如何工作的.
lua version: tio.run/
es2017 version: tio.run/
Yield 一石二鸟. 它把值返回给了调用方, 同时又从调用方拿回了新的输入. 如图所示:
Coroutine object I
Coroutine 继续执行的函数名总是相同的, 例如 "next" 或者 "resume". 这比 "step1" 或者 "step2" 来说表达力就差多了. 我们可以把 coroutine 用 object 包装起来, 这样代码看起来会好一些.
lua version: tio.run/
es2017 version: tio.run/
Coroutine object II
虽然我们添加了 "step1" 和 "step2" 这样的方法, 但是并没有强制按照这样的顺序来调用. 而且, 如果我们要给 "step1" 定义两个选项怎么办? 例如, 既可能是 "step1_add" 也可能是 "step1_sub". 让我们来把 coroutine object 升级为第 2 版.
lua version: tio.run/
es2017 version: tio.run/
我们可以看到 coroutine 和 object 是非常类似的, 但是 coroutine 更加强大. 但是没有直接的语法支持, 给 coroutine 添加 method 很笨拙.
第 1 章就到这里了. 我们使用了这些方式来实现一个极其简单的流程
- function
- object => state machine
- coroutine => coroutine object
Function 在真实世界的使用中是不足够的. 所以我们要么选择 object 要么选择 coroutine 来完整表达 "未来" 会发生的事情. Object 对于它的预期是显式定义的, 但是对于它的状态是隐式的. Coroutine 对于它的状态是显式定义的, 但是对于它的预期却是隐式的. 我们尝试把 object 和 coroutine 合并到一起, 但是成果有限.
我们还学习了多种形式的 continuation
- partial function
- object instance
- coroutine instance
Continuation 给接下来几章的复杂调度打下了基础.
来源: https://juejin.im/entry/5bcdb137f265da0ac07c8e0a