表示式建模
如果有人問你 2 * (3 + 4)
等於多少,也許你會脫口而出說等於14
, 但是你有沒有想過,計算機是如何計算這個表示式的?為什麼當你在計算機裡輸入2 * (3 + 4)
, 它也能得出14
?在思考計算機如何計算這個數學表示式之前,我們來看下人是怎麼計算出結果的。2 * (3 + 4)
本身是一串數學符號,當人看見這個表示式的時候,人腦需要解析這串數學符號,然後把這串符號轉換成人能理解的模型,最後對這些模型根據從學校學的一些規則進行計算從而得出結果。為了更形象的瞭解這個過程可以先看看下面這張圖:
當數學符號進入大腦後,2 * (3 + 4)
被解析成左右兩部分:2
與 3 + 4
以及一個乘法操作,這裡的括號定義了計算的優先順序,於是括號裡的3 + 4
被解析成由一個值為3
的模型和值為4
的模型以及一個加號操作+
。建模完成後便根據一定的規則進行計算,而這裡的規則,可能就是課堂上老師教給你的方法。而3 + 4
的結果又被送入下一步的操作,與另一個值模型2
進行一次乘法操作,最終得出結果14
。這時候,人就是一個計算機,而2 * (3 + 4)
便是輸入,在經過了解析,建模,對模型進行操作的過程後,得到輸出。
什麼是計算
在上面的例子中,一個數學表示式在碳基計算機中解析,建模並得出結論。相類似的,一臺矽基計算機讀取,執行程式,然後讀去資料,最後輸出一些資料。計算機是經過了某些計算
才能得出一些結論然後輸出資料的。所以也可以理解為計算機做的一些事就是計算。
程式
程式就是告訴計算機該做什麼或者怎麼做的一堆指令。程式設計師所編寫的程式碼,本質上和上面所說的數學表示式一樣,是一堆符號,它需要被計算機解釋成計算機能理解的語言才能讓計算機按照你的意願行動。
語法
程式雖然只是一串長長的符號(字串),但是卻是有一套自己的規則來描述哪些字串是有效的,而這些規則,就是計算機語言的語法。比如2 * (3 + 4)
中, 2
, *
, +
等等這些符號是有效的,能被識別的。
語義
語法只是程式表面上是長什麼樣的,卻不知道其含義。如果要知道含義,就需要有一種機器(譬如上述例子中的人), 能去解釋程式是如何執行的。而機器本身需要理解一套自己的語言,去這套語言去定義程式裡的規則,筆者這裡使用typescript
作為機器本身就理解的語言。
設計一種超級簡單的計算機語言
有了剛剛對程式及計算的初步瞭解,我們就來設計一種超級簡單的語言來計算2 * (3 + 4)
這個數學表示式,我們可以把整個過程分為三步:
- 輸入表示式
2 * (3 + 4)
- 將表示式進行建模
- 將模型在機器上執行
為了更方便理解,我們先借助人腦對錶達式進行建模。我們來定義一個值模型。這個值是一個不可以再歸併的東西,我們設定reducible
為false
來說明它是不可以繼續歸併。
class Value {
readonly reducible: boolean = false;
value: number;
constructor(value: number) {
this.value = value;
}
}
複製程式碼
接著再定義一個加法器,加法器的左右兩邊,連同加法操作,是一個可以繼續歸併的表示式,比如3 + 4
這個表示式可以歸併為7
。所以我們設定reducible
為true
來表示它可以進行歸併。我們還需要定義一個reduce
方法來對這個加法的左右兩邊進行歸併求值。而加號的左右兩邊,也可能需要進行歸併,所有如果一旦reducible
為true
,便需要進行歸併。具體程式碼可以看下面:
class Add {
readonly reducible: boolean = true;
left: any;
right: any;
constructor(left: any, right: any) {
this.left = left;
this.right = right;
}
reduce() {
let left = this.left;
let right = this.right;
if (left.reducible) {
return new Add(left.reduce(), right);
} else if (right.reducible) {
return new Add(left, right.reduce());
} else {
return new Value(left.value + right.value);
}
}
}
複製程式碼
然後定義一個乘法器, 乘法實現的邏輯與加法實現邏輯類似,設定reducible
為true
,並且需要實現reduce
來進行歸併。
class Multiply {
readonly reducible: boolean = true;
left: any;
right: any;
constructor(left: any, right: any) {
this.left = left;
this.right = right;
}
reduce() {
let left = this.left;
let right = this.right;
if (left.reducible) {
return new Multiply(left.reduce(), right);
} else if (right.reducible) {
return new Multiply(left, right.reduce())
} else {
return new Value(left.value * right.value);
}
}
}
複製程式碼
通過定義值,加法,乘法的描述,我們可以將2 * (3 + 4)
變成如下描述:
new Multiply(
new Value(2),
new Add(
new Value(3),
new Value(4)
)
)
複製程式碼
最後定義個抽象的機器,這個機器需要能理解我們上面的模型描述。其實只需要不斷的呼叫模型的reduce
方法直到得到最終的值即可。
class Machine {
expression: any;
constructor(expression: any) {
this.expression = expression;
}
run() {
while (this.expression.reducible) {
this.expression = this.expression.reduce();
}
console.log(this.expression);
}
}
複製程式碼
執行
new Machine(
new Multiply(
new Value(2),
new Add(
new Value(3),
new Value(4)
)
)
).run();
複製程式碼
最終得到
Value { reducible: false, value: 14 }
複製程式碼
是不是超級簡單?