編程語言的本質是什麼
作爲程序員,我們會接觸到各種各樣的語言:
我們會用 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 的擴展。
總結
我們從硬件、操作系統、編程範式三個層次來探討了編程語言的本質:
-
硬件是用電子控制機械,通過驅動程序來驅動硬件工作,而 CPU 可以描述通用的邏輯,進而控制其他硬件,我們就是通過控制 CPU 來間接控制各種硬件的,所以它叫做中央處理器。它提供的指令集所表達的邏輯叫做機器語言。
-
操作系統實現了程序的併發執行,讓一套硬件上可以同時跑多個程序,叫做進程。操作系統支持了進程、內存、IO 等各種調度。爲了安全,把程序的執行分成了用戶態和內核態兩個狀態,內核態纔可以通過驅動控制硬件,然後把它做成了系統調暴露給用戶態。各種語言的標準庫就是通過系統調用來使用操作系統的能力的。
-
機器語言是我們控制計算機最根本的方式,但是它要邏輯的表達和計算機執行細節兩方面,爲了分離兩者,我們做了編譯器 / 解釋器來完成這種轉換,這樣我們只需要表達邏輯即可,這叫做高級語言。描述邏輯有不同的方式,叫做編程範式,每種編程語言都實現了某幾種編程範式。不同編程語言的區別只是表達邏輯的方式不同,至於可用的 api,這個可以通過庫或者 runtime 來擴展。
所以,如果讓你做一門編程語言,你要做什麼呢?
-
你要先選擇一種編程範式,用它來表達邏輯,然後要設計細節的語法。
-
之後實現編譯器 / 解釋器來讓它能夠轉成控制計算機運行的機器語言。
-
之後要實現對操作系統能力做了封裝的標準庫。
-
然後如果你要表達不同領域的邏輯,則要實現不同領域的一些庫,比如圖形領域的、桌面端的、web 服務器的等等。
這是實現編程語言的思路,也是我們理解編程語言的思路。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/4OP-YXXuPy20w1MCp8Mxrw