What represents the past, present, and future: the future(1)
Our software builds a cyberspace for intelligent agents to interact with each other. Using machine learning algorithms to build intelligent agents is a hot topic. But most of the code written is still about building the virtual environment itself. The code and runtime describe what could and would happen in this environment, which is about “the future”. To solve ambiguity, code and runtime must resolve what is really happening at “the present”. The code and runtime also need to faithfully record what has happened in “the past”. To maintain the integrity and lay a solid foundation, all three things should be done properly, otherwise, we perceive the anomaly as “a bug”. Most prominent cyberspace is the online multiplayer game as the article cover suggests.
As the business interest is rapidly shifting to build more intelligent agent, the cyberspace building technique became a commodity, some good old wisdom might be lost and forgotten. The current mainstream approach is mature and working but is far from elegant. This series of three articles attempt to summarize my observation on the possible representations of “the past”, “the present” and “the future”. Some of them deserve more attention.
What represents “the future”? This seems to be a quite obvious question. The so-called business logic has many many ways to express. We are going to go through some very junior concepts from “future representation” point of view. Then we can see it is not actually as simple as we thought.
Source code
“the future” lives in the document, in the source code, and in the runtime. The language we are going to use is lua 5.3 and es2017 (ECMAScript 2017). All code can be tried online at https://tio.run/.
Simple process
We are going to describe a very trivial process: result = (a+b)*3. The “some_important_business_process” describes the process of taking a, b and computing the result.
Function
The simplest way to describe “what could and would happen” is using a function.
lua version: https://tio.run/
es2017 version: https://tio.run/
Partial function
In the previous function, we take both a and b as the input. However, we might not know all the things from the beginning. To tell the story gradually, we take “a” first and then take “b”.
lua version: https://tio.run/
es2017 version: https://tio.run/
The variable “proc” captured “a” into itself. It is a continuation which execution can continue from it.
Object
Having one initial input and one final output is not enough to describe what is to come. The complete representation of the future must describe what would happen given more input in the future. To represent this, the normal choice is to use an object to store the process, and a couple of methods to be called when the story unfolds.
lua version: https://tio.run/
es2017 version: https://tio.run/
The variable “proc” here is same as previous “proc”. It is a continuation which execution can continue from it.
State machine
However, using object does not exactly represent the process. As step1 and step2 are equal. There is no place defining step1 should happen before step2. A more accurate representation is a state machine:
lua version: https://tio.run/
es2017 version: https://tio.run/
The “next_step” is a cursor, that keeps track of execution status. In x86–64 CPU, there is an instruction pointer register called “RIP” which is similar to “next_step” concept here.
Coroutine
Coroutine works like a state machine but looks like a function. It takes much less code to express the same idea. There are two styles of coroutine, one with “yield” (also known as “generator”), one with “async/await”. We are going to look at how “yield” works first.
lua version: https://tio.run/
es2017 version: https://tio.run/
Yield kill two birds with one stone. It returns value to the caller and gets input back. Visually, it looks like this:
Coroutine object I
The coroutine is always resumed by the same function name, such as “next” or “resume”. It is less expressive than “step1” or “step2”. We can wrap the coroutine as an object to make the code looks better.
lua version: https://tio.run/
es2017 version: https://tio.run/
Coroutine object II
Although we have added method “step1” and “step2”, but they are not forced to be invoked in that order. Also, what if we want to have two options for “step1”? Say, it could be “step1_add” or “step1_sub”. Let’s upgrade the coroutine object to version 2.
lua version: https://tio.run/
es2017 version: https://tio.run/
We can see coroutine and object are very similar, and coroutine is more powerful. But without direct syntax support, writing method for coroutine is cumbersome.
This concludes the first chapter. We have used these ways to implement a very simple process
- function
- object => state machine
- coroutine => coroutine object
Function is inadequate in real-world usage. So we either choose object or coroutine to represent the future. Object is explicit about its expectation but implicit about its state. Coroutine is explicit about its state but implicit about its expectation. We have tried to merge object and coroutine into one with limited success.
Also, we have learned many forms of continuation
- partial function
- object instance
- coroutine instance
Continuation will form the foundation of complex scheduling in following chapters.
See also:
What makes good software architecture
3 Roles of Programming Languages
Top comments (0)