表达过去、现在与将来:之将来(1)

359 阅读5分钟

我们的软件构建了智能体可以彼此互动的数字空间。使用机器学习算法来构建智能机器人是一个热门的话题。但是大多数的代码仍然是用来构建这个虚拟环境自身的。代码和运行时描述了在这个环境中什么可能发生,什么将会发生,也就是它的“未来”。为了解决起义,代码和运行时必须能够回答什么是“现在”正在发生的事情。代码和运行时还必须忠实地记录“过去”发生了什么。为了保持完整性以及打下坚实基础,这三件事情都必须办得妥当,否则产生了异常现象就是bug。最显而易见的数字空间就是如题图所示的在线RPG多人游戏。

当前商业兴趣正快速转移到构建更多的智能机器人上,构建数字空间的技术业已白菜化,一些过去的智慧可能会被丢失和遗忘。当前主流的搭平台的做法行之有效而且成熟,但是还谈不上优雅。这个由三篇构成的系列尝试总结我在如何表达“过去”,“现在”和“将来”方面的观察。其中的一些做法值得更多的关注。

用啥表达“未来”?这看起来是显而易见的问题。那些所谓的业务逻辑由那么那么多的方式来表达。我们以“表达未来的方式“的视角重新审视很多初级的概念。然后我们可以看到这个问题其实没有我们想得那么简单。

源代码

“未来”体现在了文档里,在源代码里,以及在运行时中。我们将要使用的语言是lua 5.3和es2017(ECMAScript 2017)。所有的代码可以在线尝试tio.run/

简单过程

我们来看一个非常简单的过程:result = (a+b)*3。这个“some_important_business_process”描述了接收a和b然后继续出结果的过程。

Function

最简单的描述”什么可以发生以及什么将要发生“的办法就是使用Function。

lua version: tio.run/

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给接下来几章的复杂调度打下了基础。