編程語言的本質是什麼

作爲程序員,我們會接觸到各種各樣的語言:

我們會用 Javascript、Typescript 來寫前端應用,用 Java、Go 等來寫後端應用,也會用 Python 來寫一些工具腳本。

每種語言都有自己的語法和擅長的領域,那不同的編程語言的區別是什麼呢?編程語言的本質是什麼呢?

這篇文章我們嘗試探究一下。

從硬件到語言

不同的語言最終都是控制計算機的一些硬件來工作的,從硬件層面來看他們是沒有區別的。各種語言只不過描述邏輯的方式、 api 的封裝方式不同而已,底層都跑在同一套硬件上。

那硬件層面做了啥呢?

就拿打印機來說,它本身是一個機械的結構,但是控制機械工作的確是通過電子的方式,也就是 0 和 1 的電信號,一般控制它工作的芯片都有不同的引腳,傳高低電平代表不同的含義,那麼就可以通過高低電平來控制打印機做不同的工作。

控制硬件工作的程序就叫做驅動程序,它把硬件的能力做了封裝,提供了各種 api。這樣,我們就可以通過程序控制各種硬件了。

這些硬件中最特殊的是 CPU,其他硬件提供的指令都是控制該硬件工作的,而 CPU 提供的指令確是可以描述各種邏輯,可以讀寫內存,進而控制其他硬件。

這樣我們就不用直接控制其他硬件,而是可以通過 CPU 來間接的控制各種硬件,所以,它叫做中央處理器。這些機器指令叫做指令集,由它所描述的邏輯,就是機器語言。

硬件是通過電子來控制機械,提供了驅動程序,然後又通過 CPU 來實現各種通用的邏輯,進而控制其他硬件。CPU 提供的指令集所描述的邏輯,就叫做機器語言,這是我們寫的程序最底層的樣子。

爲什麼要有操作系統

計算機上肯定不能只跑一個程序,那是最早的計算機,現在的計算機都是支持多個程序的併發的。但是不管跑了多少了程序,都是在同一個硬件上跑,只是做了順序、優先級等的安排,這種叫做調度,實現多個程序的調度的程序就是操作系統。

既然是爲了讓多個程序都能跑在同一個硬件上,那得先給這個跑起來的程序一個名字,就叫做進程。進程調度是 CPU 最主要做的事情。進程要用各種硬件,那也就還需要內存調度、I/O 調度等。

總之,操作系統主要做的事情就是支持了程序的併發執行,把各種資源統一管理了起來,實現了進程、內存、IO 等等調度。

同時,爲了安全性,操作系統會把程序的運行狀態分爲用戶態和內核態,只有內核態可以訪問驅動,來控制硬件,然後提供了系統調用給用戶態來用,因爲如果任何程序都能隨意操作硬件,那就不安全了,所以要管控起來。

爲什麼講編程語言會講到操作系統呢?

因爲我們寫的應用層的代碼都是在操作系統上跑的,用的各種 api 也最終都是操作系統提供的系統調用來實現的。

但我們不是直接使用系統調用,而是用各種語言的標準庫,這些標準庫就是對系統調用做了進一步的封裝,比如創建進程、訪問網絡、訪問內存等等。Node.js 的 api、JDK 的 api 都是基於系統調用封裝的。

操作系統實現了多個程序在同一套硬件上的併發執行,爲了安全,還把程序的運行分爲了內核態和用戶態,提供了系統調用來使用操作系統能力,各種語言對系統調用做了封裝,這樣,我們就能通過這些 API 來控制計算機了。

編程範式與描述方式

我們講了如何通過機器語言來控制 CPU 進而控制其他硬件,講了操作系統的功能和它提供的系統調用是怎麼被編程語言封裝的,這些都是我們能夠控制計算機的基礎。

但是我們現在還停留在機器語言呢,用這個來寫邏輯也太麻煩了,既要考慮怎麼表達邏輯,又要考慮計算機是怎麼執行的,比如要訪問那個寄存器、讀寫哪個內存等。

能不能簡化一些呢?

首先想到的是把機器語言做成一些有含義的字符串,叫做彙編語言,這樣描述起來就簡單很多。

但是這依然要考慮表達的邏輯、計算機執行細節這兩個方面,而執行細節其實與邏輯沒關係,而且不同機器和操作系統的執行細節也可能不同。

能不能我只管怎麼表達邏輯,然後通過一種方式來轉成帶有執行細節的機器語言呢?

這種就是高級語言了,它的特點就是沒有具體的執行細節,只關心邏輯的表達,實現這種轉換的就是編譯器。(當然,也可以做成一個解釋執行其他程序的中間程序,叫做解釋器)

而描述邏輯這件事情有不同的方式,比如我可以通過一個個函數來組織邏輯,把數學那套思維拿過來,這叫函數式,也可以通過一個個對象來組織邏輯,這叫做面向對象,這些不同的描述邏輯的思路就是編程範式。

編程語言主要就是實現了某幾種編程範式,這樣,程序員就可以通過不同的方式來描述邏輯,由編譯器去轉成帶有計算機執行細節的機器代碼。

不同語言實現的編程範式不同,也就是描述邏輯的方式不同,這是語言之間最大的區別。 至於能做什麼,這個不是區別,只要對系統調用封裝一下,做成一些庫就可以支持。

比如 Javascript 最開始只可以在瀏覽器上跑,描述渲染邏輯,但後來有了 Node.js 後,它同樣可以用來描述一些腳本或者服務端邏輯。

像現在的跨端引擎,不就是對操作系統能力做了封裝,通過 Js 來描述邏輯,然後由 native 來調用操作系統能力麼?

還有 electron、hybrid 等等,這些都是 Javascript 的 runtime,他們擴展的是 api,並沒有擴展 js 語言本身。

那什麼擴展了 Javascript 語言本身呢?是 Typescript 這種編譯成 Javascript 的語言,它提供了類型系統,可以靜態檢查出程序中的一些錯誤。

爲了分離邏輯的表達和程序執行的細節,我們實現了高級語言,這樣程序員只需要專注邏輯的表達,然後通過編譯器 / 解釋器來轉換成帶有執行細節的機器語言代碼。而邏輯表達有不同的方式,比如面向對象、函數式等,每種編程語言會實現其中的幾種,這是語言之間最大的區別。語言只是表達邏輯用的,至於能做什麼,則是 api 的事情,只要對系統能力做下封裝,就可以擴展其他的 api,進而可以寫該領域的邏輯,比如 Node.js、Electron、跨端引擎等都是 api 的擴展。

總結

我們從硬件、操作系統、編程範式三個層次來探討了編程語言的本質:

所以,如果讓你做一門編程語言,你要做什麼呢?

這是實現編程語言的思路,也是我們理解編程語言的思路。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/4OP-YXXuPy20w1MCp8Mxrw