程式語言漫談

tech.youzan.com發表於2016-07-19

  寫在前邊:我們知道現有語言的程式設計正規化有:過程式,物件導向,函式式,邏輯式。隨著軟體工業化程度的普及,以及軟體的複雜度越來越高,程式語言的發展歷程也是從最初的過程式(命令式)語言c,發展到以java語言為代表的物件導向程式語言。而邏輯程式語言(以prolog為代表)和函式式語言(lisp系列)還多用在學術和人工智慧領域中。近幾年,隨著多核,雲端計算時代的到來。函數語言程式設計語言逐漸浮出水面,最經典的語言以scheme,common-lisp,ml,clojure,go為代表.而且最近的jdk8也逐步加入了 functional,closure,lambda等語法,而且scala的作者也越來越推崇編碼者以函式式語言的思維來coding。可見程式語言的發展也是滿足時代的變化不斷變化著。從其中的發展歷程中我們可以看到:技術的發展都是在圍繞著解決“軟體的複雜度”這個基本的需求而發展的。

 一、 程式語言概述

  程式語言是計算機的符號,是人和計算機交流的語言。我們學習一門新的程式語言時,應該觀察這門語言的那些特性呢?《SICP》一書的作者列舉了一下三點:

  * primitive elements. (基本元素)

  * means of combination. (組合手段)

  * means of abstraction. (抽象手段)

  以上3個特性,基本上涵蓋了所有程式語言的特性,並且也是一個語言設計者從開始就要考慮的。我對這三點的理解:primitive elements表示語言的基本符號(基本資料型別,關鍵字等)也就是詞法部分。means of combination利用基本元素通過組合的過程構建大型程式的手段,不同的語言提供的組合手段是不同的。means of abstraction表示抽象,抽象是解決軟體複雜度的重要手段,讓軟體的可讀性,可擴充套件,可重複利用等得到提升。下邊會從組合元素和抽象手段來對比語言特性。

 二、 組合手段

  組合語言:算是最簡單的詞法和語法形式了,被稱作低階語言。彙編器通過直譯的過程將彙編程式碼翻譯為native code(cpu指令集)。 提供的primitive elements有:數字,字元,-,+,*,/ , case,if, break, go,指令等基本元素;通過這些元素組合成計算機執行序列符號。

  c語言相比組合語言更高階,讓程式設計師脫離和cpu,暫存器,記憶體直接打交道的工作,提供了更多的組合手段:比如陣列,結構體等資料結構。

  java語言自稱是面嚮物件語言,所以比c語言更進一步,通過強大的型別系統手段來組合屬性和方法。 go語言和ML語言非常相仿,“介面”,"高階函式“,”閉包“,"duck type","返回多值”,並提供goroutine等來組合。

  prolog語言完全是模式匹配的邏輯語言。他的思想基於:世間所有的定理都給予一個最簡單的定理推理而來,就像數學的基礎是“1+1=2”,然後才能推匯出“萬有引力”,“相對論”等定律。

  lisp方言以s-expression(著名的S表示式)來組合資料和函式。在lisp中不區分資料和函式,一切皆為資料。

  題外話:lisp方言是和圖靈機的思想一脈相承的,編碼的時候完全感受不到計算機體系結構。而其他語言更多的是馮·洛伊曼的計算,儲存思想而設計,要麼是“堆疊”結構,要麼是“暫存器”結構;

 三、 抽象手段

  從c語言開始,以函式為單元提供了對程式的抽象。這樣大大的提高了程式的可複用,模組化等。讓團隊合作編碼也成為可能。

  物件導向程式設計:基本上隱藏了計算機的細節,開發者通過物件來抽象具體業務。但嚴格意義上來說java也屬於imperative-lang的範疇而且都是傳值呼叫。相比來說,python,ruby更物件導向一些,python融合了物件導向和函數語言程式設計正規化,更接近自然語言些。

  以lisp為代表的函式式語言:以函式為基本和唯一的抽象;common-lisp也基於巨集開發了一套object-oriented的程式設計方式。我比較傾向於函數語言程式設計理念:函式的無副作用(不用考慮執行緒安全,特別是對於變態的Haskell),高階函式,閉包,lambda等。

 四、 型別系統

  大家平時經常會說:javascript是一個弱型別的語言,java語言是強型別的語言。將程式語言從型別系統的角度區分語言也很有趣。一般來說弱型別語言更偏向自然語言一些,語法也很自由活潑些。而現今語言的走勢也趨向與弱型別方向.

  計算機是結構化很強的,堆疊上一個二進位制位的錯誤就會導致溢位,bus等錯誤。所以語言層面的自由得益於編譯器或者直譯器的功勞。比如java,c等語言有很強的編譯時型別檢測機制,強型別的好處驅使編碼人員寫出很少有語法,語義錯誤的程式碼,對IDE的支援也便捷,是大型技術團隊的合作基石。

  弱型別語言讓我們獲取了自由(不需要型別資訊),讓程式設計師少敲了許多鍵盤。自由是有代價的,編譯器或直譯器中內含型別推理(infer type); (型別推理是利用歸一方法,基於上下文中的變數顯式型別,操作符,返回值等資訊,利用棧和逐漸替換的過程來推到出型別。) 弱型別雖然可以輕鬆編譯通過(或者不需要編譯而是解釋執行),但也是有型別檢查過程的,只是將此過程延遲到執行時了。所以弱型別語言結構化不強,編碼時很難確保型別無誤,IDE支援較難。但是通過一些分析器可以不斷的檢測語法,語義錯誤,相當於達到了強型別語言的IDE效果。近幾年語言的方向朝著逐漸脫離計算機體系結構,向自然語言方向在演進,程式設計師像藝術家一樣用程式碼自由描繪。

 五、編譯/解釋

  java語言是解釋型還是編譯型的呢? 這個很難說,從java source code -> class byte code 的過程式javac編譯器的過程。但是byte code 在jvm上執行的過程可能是解釋執行也可能是編譯執行的。解釋型和編譯型的內部都遵從編譯原理的過程:詞法分析-> 語法分析->語義解析 -> 編譯器後端-> native code的過程。 但有各自的優點:

  直譯器:載入code速度快;直譯器需要維護執行時上下文等資訊。所以載入必要的程式碼,片段解釋執行。但是對於相同的程式碼都經過編譯過程就很多餘,造成時間浪費。

  編譯器:執行速度快。而且編譯器後端也更容易優化中間程式碼,因為優化過程式一個結構化過程:往往需要遍歷整個中間程式碼,整體優化程式碼,提高執行效率。

  runtime:一般來說解釋型語言需要在記憶體維護執行時上下文資訊,服務於執行過程中變數的查詢,繫結,scope等。 而編譯語言基於暫存器堆疊模型執行程式碼,基本上資料繫結都在棧結構中完成,執行速度稍微快一些; hotspot-jvm結合瞭解釋和編譯的各自優點;最先解釋執行過程,如果方法被頻繁執行,而且達到熱點(hotspot),jvm會啟動編譯過程,將次程式碼編譯為native-code,然後快取起來,下一次的呼叫直接執行即可。

  hotspot-jvm執行基於堆疊指令bytecode, 這一點也是基於跨平臺的考慮而犧牲了暫存器指令; (而基於android作業系統上的dalvik虛擬機器是基於暫存器指令的);

  所以說,語言的設計往往是一個權衡過程;獲取的“自由”越多,"犧牲“也更大。

 六、 總結:

  最初從圖靈為了解決萊布尼茨提出的:是否存在一個通用模型來解決一切計算任務這個命題,發明了圖靈機理論。到馮洛伊曼模擬人腦神經元思考過程產生第一臺基於儲存器,運算器的計算機ENIAC,至今,計算機硬體技術並沒有實質性的變化,只是隨著摩爾定律的破滅,人們發展了多級快取記憶體,多核,多cpu技術來支撐越來越大的計算任務。 在這個過程中,隨著人們對邏輯學,符號學,演算法的不斷研究,用來和計算機互動的語言也越來越抽象和豐富。我們通過這個形象的符號來抽象時間和空間,通過這個形象的符號來解決軟體的複雜性問題,通過這個符號來和計算機傳達我們的思想。

相關文章