各個編程語言都有哪些「亮點」?

鏡像問題 各個編程語言都有哪些「黑點」? - 計算機 - 知乎


潛水了好幾天沒人提Clojure,我來開個頭吧。

這幾年總是有人問我Java入門了再學個什麼語言比較好,在我自己的經歷中,Java是我的第N語言,因此談不上實際經驗。但在幾年前,我的回答是Ruby,一年前,變成了Clojure。

我的觀點是,第二語言應該選一個成熟的但與第一語言有顯著不同的,開眼界的的意義多於使用。學習的過程應該充滿了「原來這樣也可以」的驚喜,而不是「這不就是XXX里的YYY么」的虛假掌控感。更嚴重的是看了半天第二語言,回過頭來覺得第一語言是垃圾,那就是失敗中的失敗。每一種新語言的學習都應該回饋你已經入門的語言,拓展對原有語言的理解,而不應該導致你完全放棄先前的語言,否則你每看一個新語言,要麼簡單嘗試後就覺得新語言不值得學,要麼新語言對胃口舊語言是垃圾,最終結果就是要麼固化在某一個語言所形成的舒適區中,要麼各種語言都淺嘗輒止,不能深入。如果語法結構相近(比如選Groovy,Scala,Kotlin作為第二語言),很容易就陷入無聊的代碼風格喜好的比較。

而Clojure作為Java程序員的第二語言的最大亮點就是,同樣在JVM上,在可以與JAVA互操作的前提下,它有著與Java完全互補的編程概念與開發體驗。可以說是對Java的一個完美概念補完。學習了Java與Clojure後再回過頭去看大部分JVM語言,你會發現其大部分概念都落在兩者之間,而不至於每接觸一個新語言都大驚小怪的一番了(當然我只是想像,我接觸Clojure之前已經接觸過一些其他語言,包括LISP系的Scheme和Common LISP了)。況且,Clojure本身也是一個很實用的語言,我對Scheme和Common LISP就純屬是玩票,因為學了語言沒時間熟悉第三方庫,短時間內做不出什麼真正實用的東西。而Clojure可以直接使用熟悉的Java類庫。這幾年我用Ruby,Scala,Groovy,Python都寫過本地腳本,現在已經全部遷移到Clojure上,目前Clojure也是我的主力腳本語言。

下面簡單列一列(我想像中)學習和使用Clojure會給新手帶來的體驗。不過我在函數編程方面浸淫不深,本文將以基於面向對象開發背景的實用為主,高手請勿噴臉。

對比一下Java與Clojure的不同

Java:靜態類型,面向對象(基於名詞),風格嚴謹(鼓勵使用統一的最佳實踐寫法,不鼓勵使用非既定語法或模式的任何東西)

Clojure:動態類型,面向函數(基於動詞),風格鬆散(天馬行空, 在幾條最簡單的基本規則之上可以隨意構造語法元素)

亮點一:各種基本元素任意組合

Clojure是LISP的方言,如果你簡單了解過LISP,你可能已經知道LISP的程序本身就一棵語法樹,所有表達式都是「動作」帶頭的中綴表達式,比如說(+ 1 2 3 4)對應其他語言中的1+2+3+4。同樣,Clojure里提供了以下方式來調用對象方法:

(. "abc" substring 0 3) 對應java的 "abc".substring(0, 3)
(.substring "abc" 0 3) 對應java的 "abc".substring(0, 3)

第一種寫法,. 是動作(表示「調用對象方法」),然後第一個參數是對象(換言之就是this),第二個參數是方法名稱,後續的參數對應原本的參數表。

第二種寫法,方法名稱 .substring 是動作(看作一個函數),然後第一個參數是this,後續參數對應原本的參數表。

別看只是簡單的寫法變換,事實上它把Java中寫死的 對象.方法 模式打散了,你能夠清楚看到其中 「this引用」,「方法調用操作符」,「方法」 等元素以不同形式組合起來。以後接觸到Javascript里 foo.apply(obj, x ,y) 或者Java反射里的 method.invoke(obj, x, y) 之類的寫法也就順理成章了。

那麼Java里的鏈式調用怎麼辦,按上面的寫法,employees.get("Tom").getName().getFirstName() 得寫成 (.getFirstName (.getName (.get employee "Tom"))) 這得多醜。

事實上,前面說了,在Clojure里各種調用元素已經打散了,你可以用任意方法把它們組合起來。例如:

(.. employees (get "Tom") getName getFirstName)
;; .. 表示鏈式調用對象方法

(-&> employee (.get "Tom") .getName .getFirstName StringUtils/isNullOrEmpty)
;; -&> 表示鏈式調用任意函數

(some-&> employee (.get "Tom") .getName (.setFirstName "Tommy"))
;; some-&> 表示null安全的鏈式調用,調用鏈中任意一個節點結果為null則停止調用,
;; 返回null。類似Groovy里的 ?. 操作符。

其實鏈式調用的原理很簡單,定義一個動作,從一個起始值開始,把它插入到一個後續函數中作為第一個參數,再把調用結果插入到再下一個函數中作為第一個參數,依此類推即可。

特別地,some-&> 是基於宏展開為if判斷非null的方式,上述代碼宏展開後如下

而非像Groovy那樣採用catch NullPointerException的方式。使用Groovy的?.操作會干擾調試時使用NullPointerException異常斷點,一度令我非常抓狂。而Clojure的some-&>實現沒有這個問題。

當然還有更多玩法,在Java里如果想用鏈式調用串起一系列setter,你要麼得破壞JavaBean協議在setter里返回this,要麼得添加一個新方法。而且對於已經寫死了返回值為void的方法就只能幹瞪眼或者寫wrapper。而在Clojure里很簡單:

(doto employee
(.setName "Tom Parker")
(.setAge 28)
(.setPosition (Position. "Java Dev"))

;; (Position. "Java Dev") 表示調用Position的構造方法,相當於 new Position("Java Dev")

do 表示把一個值插入到後續一系列函數調用中作為第一個參數,最終返回原始的值(例子中返回employee), 這就類似於Groovy里的 employee.with {name = "Tom Parker";/*任意操作*/; it}。

進一步,我可以很方便的在一個單一結構中構造一個即用即棄的對象,設置其初始狀態,然後調用一個關鍵方法

(def employee (-&> (doto (EmployeeFactory.)
(.setType (doto (SimpleEmployeeType.)
(.setAddressRecordType "json")))
(.setRecordType "xml"))
.createInstance))

寫成java,得寫成

EmployeeFactory employeeFactory = new EmployeeFactory();
SimpleEmployeeType simpleEmployeeType = new SimpleEmployeeType();
simpleEmployeeType.setAddressRecordType("json");
employeeFactory.setType(simpleEmployeeType);
employeeFactory.setRecordType("xml");
Employee employee = employeeFactory.createInstance();

同一個單詞重複了多少次就不提了,畢竟寫Java也習慣了,最大問題是引入了兩個中間變數,我還得為它們命名。要知道編程兩大難,緩存失效與命名。

用Groovy可以寫成

def employee = new EmployeeFactory.with {
type = new SimpleEmployeeType().with {
addressRecordType = "json"
it
}
recordType = "xml"
it
}.createInstance()

寫法上倒也差不多,但Groovy使用了兩次開域方法with, 使用了兩次Lambda,資源損耗上比Java多了不少。而Clojure的代碼宏展開後與Java代碼邏輯上是完全相同的,僅僅在兩個do上使用了兩個(隱含的)局部變數。(論跑benchmark,clojure可是跟scala同一級別的 https://benchmarksgame.alioth.debian.org/u64q/compare.php?lang=clojurelang2=scala )

亮點二:編輯方便

等等,有沒有搞錯,這一堆括弧套括弧的東西你跟我說編輯方便?我連看都看不清楚!

其實作為跟C一樣古老的語言(如果不是比C更古老),LISP界早有成熟的解決方案。就以上面的代碼為例,在IDE里看起來是這樣的。

無論採用怎樣的縮進方案,結構都很清晰(看括弧顏色就行),而且因為基本元素都是獨立的,隨便怎樣斷行縮進都很自然。例如在Groovy里如果.createInstance()那裡斷一行就顯得頗為奇怪,因為.createInstance()不是一個獨立的語法元素,而它之前的new EmployeeFactory.with{}卻可以作為一個獨立元素,也就是如果在這裡斷行,讀者就會先以為def employee=new EmployeeFactory.with {}是一個完整的statement,直到看到下一行,才發現它還沒完。而Clojure里,無論你選擇斷行或不斷行,都很自然。

更重要的是,由於一個語法結構總是包含在括弧中,我可以方便地選中整個結構(子樹)。用VIM鍵位的話,跑到括弧最頂層vi(即可。而無論是Java,Groovy或Scala,你都沒有辦法在一堆並列的代碼里直接選中上面這個結構。特別是Java,不靠空行的話你在一堆代碼中目測分離出這段代碼都不容易。

那麼編輯呢,兩頭插括弧夠痛苦了吧。其實只要習慣了讀Clojure (LISP)程序,整個程序就是一棵語法樹,編輯程序就是在修改語法樹的結構,用這個角度來看,一切就括然開朗了。常用的編輯動作就幾種,一般的Clojure編輯器都會提供對應的方案。

1. 游標右邊的子樹加深一層。

用人話說就是把游標後面的語法結構兩頭套括弧,比如你有 (A (B C)), 以|表示游標

|(A (B C)) 時使用就變成 (|(A (B C))) ;; 下文用 ==&> 表示執行編輯操作後的結果
(|A (B C)) ==&> ((|A) (B C))
(A |(B C)) ==&> (A (|(B C)))

一個簡單的場景是,原來有一大段代碼

(/*幾十行代碼*/)

現在我要用if把它包起來。我只需要把游標移動到左括弧前,加深子樹,變成

(|(/*幾十行代碼*/))

繼續直接輸入if和條件就變成了

(if (some? amount) (/*幾十行代碼*/))

在其他語言一般的做法是在要移入if的代碼塊前面打出一個if語句,IDE自動生成空的花括弧。然後移動游標選擇要移入的代碼,目測保證結構完整,然後剪切移入。(也有人喜歡打if和左花括弧後不按回車,然後移動游標找到要包圍的代碼結尾,加入右花括弧。這種方法的問題是,這時被包圍的代碼還保留著原來的縮進,代碼也沒被選中。唯一方便的做法是全文自動格式化一下,而我是很不喜歡無腦全文格式化的,玩習慣了有一天換了個編輯器沒配好格式那就是給整個團隊找麻煩)。

2. 游標所在的元素提升一層,代替原來的父節點。

用人話說就是去掉包圍當前元素的代碼

(A| (B C)) ==&> A
(A (|B C)) ==&> (B C)
(A (B| C)) ==&> (A B)

基本就是1的逆操作,我有一段在try catch里的代碼,現在我不想要try catch了,一鍵解決。

3. 把當前節點的下一個相鄰兄弟變成當前節點的最後一個子節點

用人話說就是擴大右括弧的範圍

(A| B) C (D E)
==&> (A| B C) (D E)
==&> (A| B C (D E))

簡單場景:原來代碼已經有連續兩次對同一個對象的setter調用,現在我要加入第三個,我覺得應該把它們組織起來形成一個獨立結構了。

原來

(.setName employee "Tom")
(.setAge employee 35)

修改:在前面加入新的setter, 使用重複調用的do形式:

(doto employee
(.setPosition "PM"))
(.setName employee "Tom")
(.setAge employee 35)

游標移動到do處(頂層任意位置),擴大右括弧範圍兩次

(doto employee
(.setPosition "PM")
(.setName employee "Tom")
(.setAge employee 35))

刪除多餘的employee引用

(doto employee
(.setPosition "PM")
(.setName "Tom")
(.setAge 35))

4. 當前選中內容作為一個節點的子節點

用人話說就是選中內容兩頭加括弧。VIM里裝了surrounding插件用 S 即可。

這個跟第2個操作配合起來簡直爽歪歪,我需要去掉任何選中代碼兩端的包圍代碼,只需要兩頭加括弧,提升層次,去掉括弧即可。在Java里我得先從要保留的代碼開頭往上查找到父結構的起始位置,刪掉。再找到要保留代碼的結尾,往下查找到父結構的結尾(注意這時由於父結構開頭已經被刪了,整體代碼結構已經被破壞了,一不小心看走眼了就要redo重來),刪掉。

5. 針對當前節點的選擇和刪除。

就是VIM里的 da( 和 va( 。就不用展開了。

亮點三:宏

估計不少人已經聽說過LISP的宏強大,具體理論我這裡不展開了,就秀點簡單的宏吧

1. 重複用不同參數執行同一方法。

StringBuilder的sb.append的N連擊算經典了,有沒有人覺得打一堆append也很煩啊,但為了少打幾個append專門寫個方法用個for來循環調append又太大題小作。同樣某些Obserever模式代碼連續加好幾個listener,狂調register方法也很煩。

一個宏搞定:

(defmacro doto-with-method
"Invoke the same method on an object with different arguments"
[x m args]
(let [gx (gensym)
gm (if (keyword? m) (symbol (name m)) m)]
`(let [~gx ~x]
~@(map (fn [a]
(if (vector? a)
`(~gm ~gx ~@a)
(if (nil? a)
`(~gm ~gx)
`(~gm ~gx ~a))))
args)
~gx)))

用法

(def ejb-java-scanner-chain
(func/doto-with-method (CompositeJavaCodeScanner.) .add
(NameMappingScanner.)
(ObserverAnnoScanner.)))

(func/doto-with-method sb .append
"&" (. employee getName) "&")

展開效果

2. 輸出表達式和值

Java8的Stream有個peek方法,在對鏈式調用進行查錯時挺好用的。可惜不是所有對象上都有peek(在強類型系統里也沒什麼用),比如說a.getA().getB().getC()這樣的鏈式調用,我想打日誌就沒法寫a.getA().peek().getB().peek().getC()了,只能另起一行手動拷貝。Groovy這種動態類型語言可以通過修改Object的metaclass解決,屬於高級特性(不嫌丑的話也可以寫個函數解決,寫成 peek(peek(a.getA()).getB()).getC(), 當然這樣搞改起來麻煩,改回來也麻煩,一般沒人會用 )。Scala應該是可以通過隱式類型轉換來加,也不簡單。而在Clojure里簡直是順理成章的事,定義個函數

(defn dprint [x]
(print "debug =&> ")
(pp/pprint x) ;; pretty-print
x)

搞定。Clojure的鏈式調用本來就可以插函數的(方法調用與函數是同構的):(-&> a .getA .getB .getC) 改成 (-&> a .getA ddprint .getB ddprint .getC) 即可。排完錯直接刪掉就行。而且在Clojure里這個dprint用起來無比方便,我要輸出任何表達式,找到其開始括弧,一鍵在外圍套個括弧,然後直接打dprint就行了。

|(foo x y z)
==&> (|(foo x y z))
==&> (dprint| (foo x y z))
==&> (dprint (foo x |y z))
==&> (dprint (foo x (|y) z))
==&> (dprint (foo x (dprint y) z))

輸出整個foo函數的返回值和參數y的值。排錯完畢,找到原來的表達式,一鍵提升語法樹層級,就還原了,十分方便。

(dprint (|foo x (dpring y) z))
==&> |(foo x (dprint y))
==&> (foo x (dprint y|) z)
==&> (foo x y z)

以上是背景介紹,到目前為止只是使用了簡單的普通函數。這個調試函數還有個美中不足的地方,它只輸出表達式的結果,多起來分不清誰是誰。這時候宏就有用了:

(defmacro dprint [x]
`((fn [x#] do
(print (str "debug: " (quote ~x) " =&> "))
(pp/pprint x#)
x#) ~x))

跟函數版也沒差多少,使用效果是:

更進一步,鏈式取值的表達式一個個輸出也太麻煩了,我想乾脆把鏈式取值的每一步都輸出來,也不難

(defmacro d-&>
[expression forms]
(let [$ (gensym)]
`(as-&> (dprint ~expression) ~$
~@(map (fn [form]
(if (list? form)
(cons `dprint (list (cons (first form) (cons $ (rest form)))))
(cons `dprint (list (cons form (list $))))))
forms))))

使用時用d-&>代替-&>就行了。使用效果

3. 類型判斷後直接轉型

剛剛看到Kotlin提供的這個功能時還是挺驚艷的,然而轉念一想在Clojure里實現一下也是很簡單的 (Clojure雖然是動態類型語言,但如果你提供類型信息的話,編譯器會編譯為原生的Java的方法調用,有一定的查錯輔助功能並大大提供執行效率)

(defmacro when-instance?

"Instance check for a varible then cast it to the class checked.
You can optionally provide a :else cause at the end.
e.g. (when-instance? String a (println (.toUpperCase a))
(when-instance? String a (str (.toUpperCase a)) :else (str "a " (class a))"

[class var forms]
(let [forms-count (count forms)
second-last-form (if (&> forms-count 2) (nth forms (- forms-count 2)))
else-cause (if (= second-last-form :else) (last forms))
forms (keep-v forms :unless else-cause :or (take (- forms-count 2) forms))]
`(if (instance? ~class ~var)
(let [~(vary-meta var assoc :tag class) ~var]
~@forms)
~else-cause)))

4. 延遲執行

這是LISP宏的經典應用場景了,有興趣可以去讀讀相關資料,這裡只給出一個例子讓大家感受一下。

我們用代碼拼字元串時經常會遇到這樣一個場景,字元串的某一個片段包含一個關鍵部分,如果這個關鍵部分是空的,那麼整個片段都不應該出現。也就是說這個片段分為「前綴」,「主體」,「後綴」三個部分,主體為空時則全不輸出。但這三個部分求值代價不小,我希望主體只求值一次,如果其為空,則前綴和後綴部分完全不求值。

Java的代碼看起來是這樣的

StringBuilder sb = new StringBuilder();
sb.append("&");

在Clojure里,我可以做這樣一個宏

(defmacro append-sb

"Build chained appends for StringBuilder/StringBuffer.

e.g. (append-sb sb "1" 2 (str 3)) will be expended to
(-&> sb (.append "1") (.append 2) (.append (str 3)))

You can also use a map with :prefix/:main/:postfix elements, the whole map will be appended only
if the :main element is not null.

e.g. (append-sb sb 1 {:prefix 2 :main (:none {})} 3) results in "13""

[string-builder appends]
(let [$ (gensym "$)
forms (reduce
(fn [fms v]
(cond
(and (map? v)
(contains? v :main))
(let [main (:main v)]
(keep-v fms :unless main :or
(let [mv (gensym)
when-cause (as-&> ["do] wc
(if (:prefix v) (conj wc (cons ".append (cons $ (list (:prefix v))))) wc)
(conj wc (cons ".append (cons $ (list mv))))
(if (:postfix v) (conj wc (cons ".append (cons $ (list (:postfix v))))) wc))]
(conj fms
`(let [~mv (str ~main)]
(if-not (clojure.string/blank? (str ~mv))
~(into "() (reverse when-cause))
~$))))))
:else (conj fms (cons ".append (cons $ (list v))))))
[]
appends)]
`(as-&> ~string-builder ~$ ~@forms ~$)))

那麼上面的代碼就可以寫成

(doto (StringBuilder.)
(append-sb "&"))

宏展開後代碼長成這樣:

可以看到展開後的代碼把兩個main部分分別保持在了G__1721和G__1722兩個臨時變數里,然後使用if-not進行了非空判斷,if-not內部的append main 部分直接使用了臨時變數,僅對前綴和後綴求值。在大部分其他語言中,這些延時求值的表達式都得套在Lambda中。

======= 開始 PS 的分割線 ==================

PS: 如果你真的試圖在自己的環境里運行上面的宏,請注意其中keep-v也是一個自定義宏,作用是調整一下if的順序,在reduce內部讀起來更舒服一點。因為當origin非常簡單時,我希望在代碼結構上它出現在開頭,這樣可以保證這一語法結構中後半段直到結尾都是主體邏輯,而不至於留一個尾巴。事實上跟if-not是一樣的,但if-not在reduce上下文中讀起來很齣戲,我改成keep-v ... unless ... or ...,意思是「除非條件成立,否則當前的reduce結果就保持origin的值不變。如果條件成立,那麼當前結果就取下面這一大託運算的結果。」

(defmacro keep-v

"A human friendly syntax for (if pred form origin).
Usage: (keep origin :unless pred :or form)"

[origin unless-key pred or-key form]
(if (not= :unless unless-key) (throw (IllegalArgumentException. "Missing unless cause.")))
(if (not= :or or-key) (throw (IllegalArgumentException. "Missing or cause")))
`(if ~pred
~form
~origin))

======= 結束 PS 的分割線 =================

不知不覺就寫成了長篇大論,其實還有很多語言特性沒有提,例如軟體事務內存模型,惰性序列,控制台調試,動態多分派(比OOP更靈活的繼承方案)等等。回應題目,這裡只是著重結合個人經驗介紹一些開發體驗上的亮點。真的有興趣去了解語言層面上的亮點建議直接看書,正如開篇所說,Java入門後,有閑暇時間推薦優先看看Clojure。


Cirru 已經載入 GitHub 了,拿出來扯扯。

特別之處在於 Cirru 認為編程語言應該直接編輯語法樹,而不是用文本寫好代碼然後解析出來語法樹。因為樹是常用的數據結構,實際上比較容易用 GUI 設計編輯器,渲染成複雜界面,用 CSS 控制各種樣式的細節,比如自動布局,代碼摺疊等等。所以程序員們就不用為分號寫不寫,括弧寫不寫,換行不換行,而爭執了,代碼長什麼樣 CSS 直接畫好了,有什麼好操心的。

由於 Cirru 只有語言前端,沒有自己的後端,目前主要是編譯到 Clojure 或者 JavaScript 之後才能運行的,只有一個用戶。

圖形界面方案 http://cirru.org/

文本語法 http://text.cirru.org/


我就安利一個小眾語言F#吧:

F#是C#的同門師兄弟,所以我就直接拿C#來比較。亮點有:

1、let,let 的語義是符號綁定一個值,而不是聲明一個不可變變數,雖然實際上往往是通過後者實現,但是因為這個語義,我們思維上可以簡化編程的邏輯。如:

let (a, b) = (b, a)

這種東西不需要在py(念piyan)里找,F#就可以,而且編譯器優化效果很好。如

let Subtract a b =
let (a, b) = (b, a)
a - b

未優化前,ILSpy還原出的C#代碼為:

public static int Subtract(int a, int b)
{
Tuple& expr_07 = new Tuple&(b, a);
int b2 = expr_07.Item2;
return expr_07.Item1 - b2;
}

Release版為:

public static int Subtract(int a, int b)
{
return b - a;
}

完全符合let 為值綁定的語義預期。但是實測,C#中,前者代碼是不可以優化成後者的。

2、類型推導。讓代碼簡潔:

讓我們翻開老趙的舊貼使用Lambda表達式編寫遞歸函數 - Jeffrey Zhao - 博客園,看看腦袋寫的不動點組合子:(懷念一下腦袋)

static Func& Fix&(Func&, Func&&> f)
{
return x =&> f(Fix(f))(x);
}

static Func& Fix&(Func&, Func&&> f)
{
return (x, y) =&> f(Fix(f))(x, y);
}

我們換成F#怎樣呢?

let rec Y f x = f (Y f) x

破案,有時候就這麼簡單。

3、對象表達式。

你只需要一個具有某個介面的的對象,但是不想為此創造一個類嗎?C#不可以,但是F#可以。譬如,F#裡面的lock比較丑,又要括弧,又要寫fun()。

我們自己通過IDisposable介面來寫一個ulock來替代它:

let ulock lockObj =
let mutable token = false
try
Monitor.Enter (lockObj, token)
{ new IDisposable with
member __.Dispose() = Monitor.Exit lockObj }
with _ -&>
if token then Monitor.Exit lockObj
reraise()

使用時候只需要:

use __ = ulock xx
。。。。

當然,某些時候你還是不得已繼續使用lock。(使用using關鍵字調用ulock完全等價於lock,只是代碼還長一點)

4、inline。這個用法被歸類到F#泛型的靜態解析裡面。它是F#自己特有的泛型。舉個例子:

如果一個泛型函數,參數類型需要有一個名為「LongID」的屬性該怎麼做呢?正常情況下,需要這些類都繼承某個介面,在介面里聲明LongID屬性,然後再在目標類里都實現介面。但是F#就只需要:

let inline getLongID (node: ^t) = (^t:(member LongID: string) node)

不需要用到介面,因為F#是靜態語言,所以調用getLongID時,編譯器可以檢測你的對象是否具有LongID屬性,再而針對調用它的類生成重載代碼。這個功能是目前我認為最強大的,既模仿了動態語言的靈活性,又使用了靜態語言的嚴格檢測,甚至有時候能搶反射的飯碗,正確使用就可以避免介面滿天飛的情況,寫DSL時這特性很管用。但是只能F#使用,不能給C#做泛型調用,出問題後果自負。


Go:

內置並發,易用性很好,調度器幫你屏蔽了很多複雜的細節;

不用刻意的去用非同步的思維去寫非同步的代碼,寫call back。Go通過 goroutine + channel就能以同步的思維方式達到非同步的效果;

靜態編譯,部署方便,一個二進位,沒什麼依賴,比如:你經常會遇到,要使用xx,你得先在你得機子上再下載一坨xxx;沒完沒了了,越依賴越多的動態鏈接文件等等。

帶gc但卻沒有vm語言那麼龐大,占內存更少,AOT編譯,啟動快,編譯速度快。

性能不錯,開發效率也不弱,平衡取捨的恰到好處;其他語言玩的6的,上手Go沒難度,半天可上項目;

標準庫小巧精悍,代碼質量高,沒有層次不齊的情況,基本上涵蓋服務端編程所需,沒有的輪子也可以很快的利用它根據所需快速的造出來;

tooling的支持,battery included。Go官方一整套工具鏈,可以和其他編輯器,IDE靈活搭配,編輯器裡面照樣可以和ide那樣代碼跳轉,重構,分析以及調試;

gofmt:你使用過之後,你一定會愛上它。再推薦個goimports;

對語法特性敢於說不,即使站在風口浪尖。幾乎所有編程語言進化過程中最終都會變成堆砌語法特性,語言的正交性,簡單優雅卻在不斷喪失。從這個角度來說,lisp是美麗的語言,C也有它的美。

今日的python也早已不副當初的箴言。Go有所缺失,但還沒人說它臃腫。

吉祥物有時很萌,有時又有點邪惡的感覺。

最喜歡的一點:語言小巧,使用起來比較輕快靈活,像是在用動態語言的感覺。


Python

0. 一行代碼即可向世界問好

&>&>&> print("Hello, World!")
Hello, World!

什麼 import 啊 include 啊都不需要

1. 變數不需要聲明,隨時隨地添加

&>&>&> class C:
... def __init__(self):
... self.data = "Some data"
...
&>&>&> obj = C()
&>&>&> obj.extra_data = "Some extra data"
&>&>&> print(obj.extra_data)
Some extra data

2. 整數多大都不怕

&>&>&> 2**1000
10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376

小數要精確也不怕:

&>&>&> from decimal import Decimal, getcontext
&>&>&> getcontext().prec = 100
&>&>&> Decimal(2)**Decimal(0.5)
Decimal("1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641573")

3. 便捷的循環語句

讓用戶輸入 4 個整數:

&>&>&> para = [int(input("para[%d]: " % i)) for i in range(4)]
para[0]: 1234
para[1]: 4321
para[2]: 1000
para[3]: 0
&>&>&> print(para)
[1234, 4321, 1000, 0]

找出首字母為 "A" 的單詞:

&>&>&> words = ["Apple", "Bob", "Alice", "Python"]
&>&>&> print([word for word in words if word[0] == "A"])
["Apple", "Alice"]

4. 隨手定義匿名函數

求函數 y=(x + 1) cdot (x - 2)x=1,2,3,cdots9,10 處的值:

&>&>&> y = list(map(lambda x: (x + 1)*(x - 2), range(1, 11)))
&>&>&> print(y)
[-2, 0, 4, 10, 18, 28, 40, 54, 70, 88]

5. 完善的異常機制避免了冗長的 if

try:
print("Data: ", a.data[key])
except AttributeError:
print("Data not found!")
except KeyError:
print("Data exists, but not data with key "%d"." % key)

要是用 if 的話,就是:

if not hasattr(a, "data"):
...
elif key not in a.data.keys():
...
else:
...

6. 不需要知道這個類是什麼,只需要知道它有什麼功能

譬如說寫遊戲的時候,可以把所有遊戲對象都扔到一個 list 里,然後主函數里每次循環調用一遍 move。

&>&>&> class Block:
... def __init__(self, x, y):
... self.x = x
... self.y = y
... def __repr__(self):
... return "%s at point (%d, %d)" % (type(self).__name__, self.x, self.y)
...
&>&>&> class Man(Block):
... def move(self):
... self.x += 10
...
&>&>&> class Car(Block):
... def move(self):
... self.y += 100
...
&>&>&> objects = [Man(30,6), Car(10, 20), Car(-20,0)]
&>&>&> print(objects)
[Man at point (30, 6), Car at point (10, 20), Car at point (-20, 0)]
&>&>&> [obj.move() for obj in objects]
[None, None, None]
&>&>&> print(objects)
[Man at point (40, 6), Car at point (10, 120), Car at point (-20, 100)]

要是用 C++ 的話,還需要用虛函數,而且需要一定技巧才能把它們放到同一個 vector 里。

7. 輕輕鬆鬆保存對象

繼續上面的例子:

&>&>&> import pickle

&>&>&> string = pickle.dumps(objects)
&>&>&> print(string)
b"x80x03]qx00(c__main__
Man
qx01)x81qx02}qx03(Xx01x00x00x00yqx04Kx06Xx01x00x00x00xqx05K(ubc__main__
Car
qx06)x81qx07}qx08(hx04Kxhx05K
ubhx06)x81q }q
(hx04Kdhx05Jxecxffxffxffube."

&>&>&> objects2 = pickle.loads(string)
&>&>&> objects2
[Man at point (40, 6), Car at point (10, 120), Car at point (-20, 100)]

把 string 扔到文件里就相當於保存遊戲進度了。


沒有人詳細說說Rust么? 正好用這個帖把我在知乎的Rust相關收藏整理分享一下:

  • 從語言的設計上保證了內存安全,消滅了segment fault滋生的土壤,這個主要是ownership,lifetime和比較完善的類型系統的設計:所有權——rust編程——知乎專欄 萬變不離其宗——rust編程——知乎專欄 生命周期標記——rust編程——知乎專欄
  • safe和unsafe的設計,讓人明確的知道哪些是安全線之內,哪些不是,方便排查:Rust 和 C++ 有哪些優劣?
  • 零代價抽象,也就是說抽象的同時不影響執行的效率;Abstraction without overhead: traits in Rust
  • 內存泄露雖然可能,但相對較難; 泄漏——rust編程——知乎專欄
  • 很輕的runtime,不帶GC,採用RAII自動內存管理;RAII | 通過例子學 Rust
  • 取消了類的繼承,也消滅了因繼承而來的所有問題;Rust語言是否具有繼承? - Rust(編程語言) - 知乎
  • 很強大的衛生宏系統;初探macro——rust編程——知乎專欄
  • 吸收了函數式編程的一些特點,可OO,可functional;http://science.raphael.poss.name/rust-for-functional-programmers.html
  • 上得廳堂,下得廚房,上可以完全的當一個類似C++的高級語言使用,下可以聲明不用標準庫,裸寫底層代碼當C用,比如用Rust寫的操作系統 redox-os/redox

最後,大多數錯誤都可以在編譯期找出。C++經常會有Runtime error,但是到Rust這裡很多類似的錯誤過不了編譯器這一關。因為錯誤總是發現的越早越好,原則上說這是個好事,實際上是這樣的:

C++:編譯的時候明明好好的,為什麼運行起來就報錯!這讓我查起來多麻煩!

Rust:編譯器我求求你了,先讓我通過了先跑起來再說吧……


  • android開發吧:有問題為什麼不先問問隔壁Java吧呢?
  • Java吧:有問題為什麼不先問問隔壁C++吧呢?
  • C++吧:有問題為什麼不先問問隔壁C語言吧呢?
  • C語言吧:有問題為什麼不先問問神奇海螺呢?


補充:知乎日常黑Perl,不過我不知道有多少人真的對Perl比較熟悉。實際上,我感覺Perl在語言層面有很多優點,use strict之後也很嚴格。主要的缺陷其實是API極其噁心,寫個擴展巨麻煩。

================================

Perl可以在命令行里直接輸入腳本內容,比如這樣就會給你:

$ perl -E "say foreach 1..7"
1
2
3
4
5
6
7

實用一點的,比如我突然想要十個隨機數:

$ perl -E "say rand foreach 1..10"

另外,Perl可以工作在行處理模式,這樣你可以使用單一語言代替Unix通常的行處理工具,像sed、grep、awk等等。我現在想僅僅列印一個tab分割文件的第二列:

$ perl -F " " -E "say $F[1]" &

我想計算第一列加第二列,把兩列輸入和輸出打成三列:

$ perl -F " " -E "say join " ", $F[0],$F[1],$F[0]+$F[1]" &

只要deep_dark開頭的行:

$ perl -E "print if /^deep_dark/" &

另外,Perl很多語言特性適合寫日常腳本。比如我認為Perl的Lambda比Python的好用。我想按比較複雜的方式排序一個列表:

# 假設列表裡每個元素都是一個哈希引用,裡面有foo和bar兩個值,前者是數字,後者是字元串。
# 我想先按foo排,後按bar排:
my @result_list = sort {
$a-&>{foo} &<=&> $b-&>{foo} or $a-&>{bar} cmp $b-&>{bar}
} @input_list;

過濾一個數組,依據為元素是否同時存在於兩個哈希表裡:

my %wanted1;
my %wanted2;
my @result_list = grep {
exists $wanted1{$_} and exists $wanted2{$_}
} @input_list;

一個數值數組,我想把自身和+1、+2的值交錯地存起來:

my @result_list = map {$_, $_+1, $_+2} @input_list;

有種特別的「成功或者去死」語法,比如確認打開文件:

open my $handle, "&<", "input_file" or die "failed to open input_file because $!";

確保匹配:

$content =~ /some_(regular)_expression/ or die "failed to parse $content";
my $wanted_part = $1; # 你在這裡得到"regular"

為此,Perl的and、or關鍵字的優先順序比很多操作符都低。


其實每個語言都有很多有意思的地方,關鍵看自己會不會玩。比如:

Python:

Y = lambda fn: (lambda f: f(f))(lambda f: fn(lambda s: f(f)(s)))

Y(lambda g:
lambda s:
s if len(s)==0
else g(s[:len(s)-1]) + s[len(s)-1].upper())("abcdefghijklmnopqrstuvwxyz.")

JavaScript:

const Y = function(fn) {
return (function (f) {
return f(f);
} (function (f) {
return fn(function (s) {
return f(f)(s);
});
}));
}

Y(function(g) {
return function(s) {
let n = s.length;
if (n === 0) {
return s;
}
return g(s.substring(0, n-1)) + s.substring(n-1).toUpperCase();
}
})("abcdefghijklmnopqrstuvwxyz.")

Scheme:

(define Y
(lambda (fn)
((lambda (f)
(f f)) (lambda (f)
(fn (lambda (s) ((f f) s)))))))

((Y (lambda (g)
(lambda (s)
(cond
((null? s) "())
(else
(cons (string-upcase (car s)) (g (cdr s)))))))) "("a" "b" "c" "d" "e"))


要論亮點,那就不得不提 Scala。

  • 真的省,省了又省
    • 結尾不想寫分號,那就不寫
    • 不想寫數據類型,能推導出來就可以不寫
    • 不想寫 return,省掉,返回最後一個表達式的值
    • 沒參數的函數,調用的時候幹嘛還要寫小括弧?寫個函數名夠了
    • 有參數的也不想寫?那你打個空格吧
    • 定義類的時候每個成員都要在構造函數裡面再寫一遍,多麻煩。這樣玩多好:

class Foo(val bar: Int, val baz: String){}

    • 構造的時候 new 也懶得寫,那你用 Case Class 吧
    • 是個單例啊,直接用 Object 吧
  • 萬能的下劃線
    • 代替null

      val foo: String = _

    • 代替變數

      List(1, 2, 3).reduce(_ + _)
      如果傳入的函數只有需要一個參數的話,下劃線也省了吧,比如
      List(1,2,3).foreach(println)

    • 代替 import 的中的 * ( Scala 中的import 不只限於放在開頭,而且支持重命名)

      import com.oo.xx.{ Foo =&> Bar , _ }

    • 模式匹配

      foo match {
      case Some(_) =&> true
      case _ =&> false
      }

    • 代替類型

      trait Functor[C[_]]{
      def op[P, Q](x: C[P])(f: P =&> Q): C[Q]
      }

  • 雖然具有極高的靈活性,但是是強類型的語言。不會像 Python 一樣在運行期拋出 TypeError
  • 支持面向對象與函數式編程兩大幫派。兩者寫在一起卻能毫無違和感。
  • 高度兼容 Java ,還是熟悉的類庫
  • 最重要的,每一行 Scala 代碼,都要經過編譯器近 30 道工序的處理方能上桌,編譯速度十分感人,堪比 C++,畢竟

xkcd: Compiling


總感覺C#可以做到其它編程語言的很多亮點啊

@邱超凡 Ruby:

別的不太確定,不過關於擴展類型的方面,定義個extension method也能做到:

WriteLine(1.ToJson());
WriteLine(null.ToJson());
WriteLine(new JArray
{
new { elder = 1926, qinding = false },
"Excited!"
}.ToJson());

(好吧關於可變類型數組是有少量噪音)

面向Prototype和鴨子類型這個是亮點還是黑點暫且不論,不過因為違背強類型就不用使用C#了

@樓宇 Python:

0. 一行代碼即可向世界問好

script模式下的C#會默認做常用的reference和using

1. 變數不需要聲明,隨時隨地添加

說實話,沒有了古典C時代的「變數必須定義在過程開頭」這種限制,和隱式類型聲明之後,這個特性已經沒多大意義了

2. 整數多大都不怕

小數要精確也不怕

整數這個……BigInteger字面值,我承認有字面值是一件舒服的事情

小數嘛……有人做BigDecimal

3. 便捷的循環語句

讓用戶輸入 4 個整數:

using static System.Linq.Enumerable;//放前面是為了下一行簡潔
var para = Range(0, 4).Select(i =&> int.Parse(Input($"para{i}: "))).ToArray();

配合一下自定義Input就可以了(

string Input(string prompt)
{
Console.Write(prompt);
return Console.ReadLine();
}

找出首字母為 "A" 的單詞:

words.Where(word =&> word[0] == "A");

4. 隨手定義匿名函數

var y = Range(1, 10).Select(x =&> (x + 1)*(x - 2)).ToList();

5. 完善的異常機制避免了冗長的 if

try
{
...
}
catch (AException e)
{
WriteLine(e.Message);
}
catch (BException e)
{
WriteLine(e.StatusNumber);
}

呃,你跟我說完善

6. 不需要知道這個類是什麼,只需要知道它有什麼功能

一個List&解決的事情,都有共同基類了

7. 輕輕鬆鬆保存對象

var s = new XmlSerializer();//各種Serializer都可以
var str = s.Serialize(objects);
var objects2 = s.Deserialize(str) as List&;

至於Java……2017年了,在C#面前Java語言真的還有提的必要嗎


講一下Python。

Python,語言本身寫著爽,有舒適的握把和合適的重量。

需要借巨人肩膀一用的時候庫不僅強大而且調用簡單

巨人也種類繁多,領域廣

還有很多人和你一起騎著巨人溜達並交流心得體會,他的社區和用戶活躍

1. 寫著爽。

動態類型,就是一個變數你想存什麼就存什麼。

ditto = 0
ditto = "diiiiiiiiitto"
ditto = []
ditto = {}

語言簡練,很少的代碼就能把你想做的功能寫出來。

這個就不舉例了,隨便寫點什麼你自然就懂。

有各種好用的語法糖,比如交換兩數不用另外的變數也不用異或。

a, b = b, a

環境配置也沒有那麼麻煩,不會環境一小時代碼五分鐘。

pip不行也可以下編譯好的包,比如這個網站:http://www.lfd.uci.edu/~gohlke/pythonlibs/

2. 庫不僅強大而且調用簡單。

想做什麼import一下就好了,還有漫畫吐槽這件事。

這張圖的意思是:一個人飛在了天上,而原因是用Python運行了一句命令:「import 反重力」。

另外補一句,運行"import antigravity"是真的會有事情發生。

3. 領域廣。

雖然不至於什麼都能做,但也差不多了。

下個視頻有you-get,

抓點數據有requests,

寫個後台有flask,

加個GUI有pyqt,

打包成exe有pyinstaller,

寫個文檔有mkdocs,

換換環境有pyenv,

圖像處理opencv,

自然語言jieba,

深度學習TensorFlow,

...

還是一個膠水語言。實在有需要別的語言的地方,調用也很方便。

4. 社區和用戶活躍。

技術分享的文章、各類的群、各種相關網站都很活躍,中文資料也很多。

問題能很快被解決。提問經常能找到前人的解答,即使沒有也會有野生的大神。

學習資料豐富。關鍵詞一搜能找到各類資訊和學習的線路,選一個喜歡的就可以了。

項目也會發展較快。

就拿自己開發的微信介面來說吧,在開源過程當中碰到了很多用別的語言開發的開發者。

項目完整度、文檔、易用性都很棒,但是用的人真心少。

用戶量也在不斷增加。

各類的語言排行榜中Python的排名都在上升。

據說有些學校打算將Python開為基礎課程。

另,

我當年是怎麼喜歡上Python的?

就是幾個巨人疊著把我抬了上去,我輕輕的揮了一下剛撿到的小武器就切下了星星。

真是糟糕的成就感。


以下都是自己的體會,如果有不正確的地方,還請指教。

Python

python 這門語言的亮點就是強大的膠水能力,或者設計之初一個很大的目標就是為了能輕鬆自如的調用 c/c++ 動態庫。加上他靈活的語法很適合做服務端腳本。記得之前 google 就是核心演算法用 c++ ,然後用 python 把各個模塊組合起來。

PHP

我個人不是很喜歡 PHP, 但不得不說用 PHP 搭建一個 web 項目就是快。在小型的項目里,線上改服務端代碼就可以直接生效。相關衍生的 web 庫也很多,但隨著 nodejs 的發展,php 的市場也一點點在被蠶食。

C

鼻祖級編程語言,現在大多數的語言都是 C-like,語法上很多都是從 C 語言里衍生來的。C 語言本身沒有提供太多像「繼承」、「泛型」、「原型」等複雜的概念,除了指針其它都非常好理解。它的特點就是非常底層,運行速度快,但同樣也很難操控。現在比較適合做嵌入式開發或者教學用途。

C++

運用最廣泛的編譯語言之一,與 C 語法兼容,在其之上提供了「類」、「模板」等功能,主要運用在運行速度要求比較高但 C 又不能滿足其開發效率的場景。大部分語言都支持調用 C++ 模塊,所以很多通用庫也都是用 C++ 寫的。

Java

號稱一次編譯到處運行,但運行環境需要裝 jvm 虛擬機。運行效率稍微比 C++ 低一點,但由於有 GC,而且語法相對簡單,所以開發效率比 C++ 高一些。主要運用的場景是 android 移動端和服務端開發。

C#

微軟當初想要參與 Java 的開發,但是卻別人踢出來。後來微軟自己搞的一套類似 Java 的語言,因為有微軟的支持,功能、運行效率上都比 Java 要好。現在主要運用場景是 windows 開發,unity 遊戲開發。在微軟將 .net core 開源之後相信會有更大的發展空間。

Javascript

本人的主攻語言,語言本身設計極不合理,但它是netscape 和 IE 大戰後遺留的瀏覽器事實標準。雖然各個開發商都想用自己的語言替換掉它,但沒有任何廠商願意支持別人的方案,前仆後繼 VBScript, Flash,Dart 都死掉了。反而隨著瀏覽器不斷的發展, JS 的也一直在演進。目前主要有 ES3, ES5, ES2015, ES2016 等幾個版本,以 JS 為目標的編譯語言更是不計其數,比如 coffeScript,TypeScript, Clojurescript。

Go

google 出品的編譯語言,目標是取代 C++ 系統級開發語言的位置。特點是設計風格非常「工程化」,比如隱聲明式介面繼承,基於協程對並發的良好支持,隱式聲明共有、私有方法。目前最大的問題就是 GC 時間太長,但隨著版本的迭代,GC 時間也在一點點的減少。

Rust

與 Go 一樣,也是為了取代 C++ 系統級語言的開發地位。但與 Go 不同的是他針對的場景不同,rust 更加追求運行時的高效和安全。號稱只要編譯通過就不會有內存泄漏或者多線程衝突,但代價就是學習曲線陡峭,對於習慣了有 GC 兜底的人來說簡直就是噩夢。

Lisp

第一個函數式編程語言,語法上與 C 大相徑庭。但其它很多語言或多或少都借鑒了它的思想。語法簡單,所有操作、函數都可以通過「Lisp 的七個公理」推導出來。

Haskell

純函數式語言,不是很了解,但據說很強大。


Ruby.

這個語言沒有那麼大眾化。相比於為Web而生的PHP,瀏覽器欽定的JavaScript和準備在計算機入門語言及科研人士的編程語言中越走越遠的Python相比,一個大學裡的學妹問學長在課程之外應該學學什麼東西提高自己的知識水平,學長有極大概率不會推薦Ruby。所以反過來,能給你推薦Ruby的人靠譜的概率會大一點……

好吧扯遠了。相信大部分人接觸Ruby的時候都是因為Ruby on Rails這個閃亮的框架。無人能說清楚到底是Ruby成就了Rails還是Rails成就了Ruby。不過Rails之父DHH一開始選擇用Ruby而放棄了PHP用於公司業務的時候,一定是被植根於這個語言深處的某些設計思想打動了。儘管Rails到現在有無數人批評它過時老舊、龜速臃腫,和其他語言的Web框架好像也沒有什麼代差。不過須知Ruby on Rails開源發布的時候是2004年,想一想那個時代的Web技術吧。

某種程度上,Ruby是Smalltalk、Perl和Lisp的混合體,分別象徵著面向對象、腳本語言和函數式的思想。編程語言的歷史問題可不能說太多,容易讓人摸不著頭腦。像我幾年以前看過一本Go的書,一開始講了很長一段的Plan9,彷彿在講述編程語言界的艾澤拉斯。Ruby並不像某些語言一樣吸取無數語法然後雜糅在一起。相反,這些特性在Ruby里體現得非常自然,也許同松本行弘受C++影響比較深有關。

強大的DSL能力

DSL(領域特定語言)這個概念不是從Ruby這裡誕生的,但是在Ruby社區這裡受到了格外地強調。

every :day, :at =&> "12:20am", :roles =&> [:app] do
rake "app_server:task"
end

這個是用以在固定時間運行任務的javan/whenever包,有沒有覺得讀起來很像英語?

或者說,在Rails裡面,我們還可以用

3.hours.ago

來表示三小時前的時間。

眾多Ruby的庫和框架無不以寫得像英語為目標。喪心病狂的Rails甚至專門有一部分是處理單複數轉換的!這些東西看上去花哨,而實際上無非是用方法調用、代碼塊、Hash鍵值對的混合實現的,因為可以省略方法調用的括弧和Hash的花括弧,看起來異常地清爽。

動態,徹底的動態

上面那個是怎麼做到的?Ruby允許對自帶類型進行擴展,並且任何東西本質上都是一個對象,包括整數、字元串的字面量。(說到這裡又開始期待C++的Uniform Syntax Function Call了)像我們的整數類型,它具有to_s、to_c這樣的方法,可以把整數轉換成字元串或者複數。而Ruby是一個動態強類型語言,在這一點上比較像Python。Ruby的空是nil,鍵值對是Hash,數組是Array……這一點和JSON挺對應的。那我們要是想把對象序列化成JSON字元串該怎麼辦呢?於是,我們可以

require "json"
puts 1.to_json
puts nil.to_json
puts [{elder: 1926, qinding: false}, "Excited!"].to_json

一旦包含了JSON這個包,這些對象就會自動新增一個叫做to_json的方法,返回其對應的JSON字元串。

事實上,要擴展這些已有類型,並不是標準庫或者Rails才有的複雜手法。你只需要「再次定義」那個類即可。比方說,Ruby里所有整數其實都是Fixnum類的實例,那麼我們可以:

class Fixnum
def excited!
self.times do
puts "+1s"
end
end
end

# 3.excited!
# +1s
# +1s
# +1s

Excited!

在其他語言裡面總是顯得和語言特性格格不入的反射功能在Ruby這裡也非常地自然:

puts 1.class # class即是這個對象的類型

# 定義了名字分別為abcdefg的七個方法,輸出自己的名字
"abcdefg".each_char do |c|
define_method :c do
puts c
end
end

面向對象

Ruby裡面的「類型」不是什麼奇奇怪怪需要額外語言特性操作的東西,所有的class都是Class類的實例,包括它本身。所以有了類方法和實例方法的區別,所以創建對象不需要額外的new語法,SomeClass.new就行,這個new方法是從Object基類繼承下來的,語法具有高度的一致性。

# String類型本身也有類型,就是Class
puts String.class
# =&> Class

# Class自己的類型也是Class
puts Class.class
# =&> Class

# Class「是」一個Object,因為從Object繼承下來,
puts Class.ancestors
# =&> [Class, Module, Object, Kernel, BasicObject]

# Object也「是」一個Class
puts Object.class
# =&> Class

同時Ruby也十分強調鴨子類型。

class Dictator
def walk; end
def swim; end
def call; end
end

class Frog
def walk; end
def swim; end
def call; end
end

def wallace(person)
right = [:walk, :swim, :call].all? do |action|
person.respond_to? action
end
if right
puts "ha"
else
puts "gua"
end
end

當然,畢竟受Perl影響很大,Ruby也有很多奇奇怪怪的一行寫法,可以參見這裡Ruby One-Liners。


說說Lua。
作為一個遊戲開發者,我對Lua非常熟悉。
Lua有很多亮點,下面這幾個,最值得說一說。

--
Lua非常「小」。
--

有多小?

源碼包只有200多KB:

二進位包也只有200KB左右:

帶有豐富示例的完整語法描述,列印成A4紙不到20頁。(是的我打過~手動哭笑不得)

Lua:小就是美,對不對,對不對嘛?

--
Lua性能高。
--

在解釋型語言中,Lua是性能很高的。在遊戲領域,Lua常常被拿來和JS做對比。
JS比Lua的好處是,瀏覽器也可以運行。
但是Lua比JS的好處是,性能高,而且語言簡單,容易上手。
LuaJIT還可以執行編譯後的Lua位元組碼,速度更快。
參見這裡的討論: Cocos2dx+lua合適還是Cocos2dx+js合適? - JavaScript - 知乎

--
Lua不限制編程範式。

--

很多語言對類提供了語言一級的支持,Lua並沒有這麼做。
Lua是基於原型的語言(prototype-based language)。在Lua里非常容易地模擬面向對象。

這裡安利一下我以前寫的lua-oop: dingshukai/lua-oop
一個面向對象編程的lua框架,支持多繼承,性能棒棒噠,在多個線上遊戲中使用。
我本人非常喜歡基於組件的編程,這個說起來又是一個長篇(贊多的話可以考慮寫一寫。。。),我在lua中實現組件編程的方式就是多繼承。非常好用!
另外,在寫這個框架之前,我使用過lua官方網站推薦的幾個面向對象庫,基本都是玩具級的,在複雜使用環境下,性能瓶頸大。
所以,就寫了這麼一個框架,遊戲開發狗可以試試。

--
Table是Lua里最基本的、最常用的、幾乎唯一的、幾乎萬能的數據結構。
--

這麼神奇的數據結構,到底怎麼構造的?

簡單來說,Table是數組+哈希表。

下面是隨手寫的示例代碼:

看到前面有同學提到了PHP的Array。

看到前面有同學提到了PHP的Array。

這裡強調一下,Lua的Table和PHP的Array相比,表達能力差不多,但是性能更好,因為存儲結構不同。

Lua的Table,是·真·數組+·真·哈希,即以數組方式寫的元素放在數組裡,以key-value方式寫的元素,放在哈希表中。這個哈希表是無序的。

PHP的Array,官方文檔有提到:

PHP的Array是有序關聯數組,針對不同的使用情況做了優化。因為是有序的,必然不如hash快。

PHP的Array是有序關聯數組,針對不同的使用情況做了優化。因為是有序的,必然不如hash快。

最後,貼一段我以前遊戲里的UI布局文件,模仿CSS,支持自定義控制項,屏幕自適應,開發UI效率奇高無比。整個都是以Table寫成的。

-- Lua中的注釋是以兩個減號開頭的。

-- Lua中的注釋是以兩個減號開頭的。

再說說Java。

/**
* 使用最廣泛的語言。
*/

Java的應用場景廣泛,行業多種多樣,各種平台都有。Java的使用人數也是最多的。

證據一: TIOBE語言排行榜:

在排行榜上,Java多年佔據第一。

證據二:IT培訓市場。
IT培訓中業務最多的就是Java,就業起步工資雖然不高,但是工作機會多啊,什麼移動開發、網站開發、企業軟體開發、遊戲開發等等,都有java的用武之地。
光Java培訓就可以容納幾個上市公司,我說的還是中國。
英語作為一門人類語言,其培訓市場非常大,而Java作為一門計算機語言,如果只看培訓的話,可以與英語匹敵了。。。(大霧)

/**
* 最容易學的全能語言。
*/

剛才提到了培訓市場,也從側面印證了這一點。
下面說的這些話,就很主觀了。
如果只考慮全能語言,Java即使不是最好學的,也是最好學的之一。
很多語言也很好學,比如Lua,但是應用領域相對較窄。
Bash是不是很簡單?但是對初學者非常不友好,可能少一個空格就跑不起來。
Python呢?從網上複製粘貼的代碼,居然不能直接用!不友好!(格式丟失造成的。。)
C和C++就不用說了,初學者可能連編譯錯誤和鏈接錯誤都分不清。
當然PHP、JS這些都是相對容易入門的,但是其應用領域都不如Java廣泛。
Java:比我好學的沒有我應用廣泛,比我應用廣泛的沒有我好學。完美的平衡!

/**

* 造就了(曾經長期是)最先進的、最好用的IDE:Eclipse。

*/

大家可以回想一下,自己使用的IDE是什麼時候開始支持變數/函數重命名呢?

大家可以回想一下,自己使用的IDE是什麼時候開始支持變數/函數重命名呢?
詳細的時間很難去考證了,記憶中至少在2004年的eclipse是支持這一功能的。
再想想,游標移動到一個名字上,這個名字所有出現的位置都高亮,你的IDE是什麼時候支持的?
其他還有:查看一個名字的所有出現地方(Find References in...)、跳轉到定義、快速打開類、查看類結構、提取函數等等。
在十多年後的今天,vs2015的C++編輯器,這些功能有了一部分,但是有時候很卡、或者不全。
也許是因為C++太複雜不好實現這些功能,但是情況就是這樣,用Eclipse開發起來非常快。
如果只說Eclipse可能有同學不服:IDEA比Eclipse好用多了!
嗯,IDEA也是非常好用的,Eclipse甚至向IDEA以及早期的JBuilder借鑒了很多東西。
但是Eclipse還有一個很厲害的地方:Eclipse是一個平台。

Eclipse整個是插件式的結構,我大概記得,零幾年曾經有一本書專門介紹了Eclipse插件開發,這本書還獲得了jolt award。但是現在我已經查不到這本書了。

有很多工具是基於eclipse開發的:

上圖中總有幾個工具你眼熟吧?ADT、Adobe Flex我都用過。

上圖中總有幾個工具你眼熟吧?ADT、Adobe Flex我都用過。
而這只是以字母A開頭的基於Eclipse的工具,全部列下來會非常長的。。。

/**

* 孕育了一批先進的軟體開發思想:測試驅動開發、極限編程、敏捷開發、重構等等。

*/

這些思想,都是起源於Java,或者最先大規模應用於Java。

測試驅動開發,是以Java作為例子的,JUnit是主流語言中使用最早最廣泛的單元測試框架。後面其他語言的各種Unit雨後春筍一樣冒出來。

那本講重構的書,就是以Java為例子的。

不得不說,當年這些概念十分火熱,催生了一大批佈道師、技術書籍、軟體諮詢公司。。。

岳雲鵬:word天哪,這麼多牛逼的書,都是講Java的嗎!

我的公眾號dingshukai888,教編程,歡迎關注~

http://weixin.qq.com/r/FDssNGXEljjprSFX924G (二維碼自動識別)


怎麼還沒人說Java?

那我來說說Java。

寫過挺多語言的,Java、C、Js、Node、Python、Scala 可是寫的最多的還是Java。

1.靜態類型

Java的類型是靜態類型的,因此如果定義一個變數不能像js、py等這樣定義動態類型:

int a = 10;
String b = "hello world";

而換成Js

var a = 10;
var b = "hello world";

看似這顯得很啰嗦,可是當你工作了你們好幾個人一起寫了快一年的代碼的時候,你會發現Java雖然啰嗦,但是語法簡單,嚴謹,這可以讓你們這個big project避免很多不必要的bug。特別是當突然出現了一個bug排查起來也是方便很多的。

以上這種靜態類型能讓你的IDE在編譯期間更好的發現問題並且提示你錯誤,而不是在運行的時候才拋出異常。

2.上手簡單

學過C以後來學Java真會覺得很容易上手,做大項目的時候多個人合作,大家寫出來的大部分(非核心)代碼差距不會過大,這也是為什麼那麼多企業選Java做為主要的開發語言吧。而其他語言團隊中的差距可能會被拉大,python大神可以寫的很飄逸,新手寫30行到大神手裡寫可能只要1行,這個時候如果出bug了,教給別人去排查代碼問題可真是頭疼。

3.第三方豐富

Java第三方真是豐富。各種完善的框架,想要做大數據,離線計算上Spark、實時計算上Storm、搞個分散式協調直接上ZK或etcd、分散式隊列有kafka …… 至於J2EE方面,有Hibernate、Mybatis、Spring,多線程又有各種現成的多線程框架。

而相比node等語言,不是說其他語言不好,但是在搭建這麼大一個系統的時候選擇真的是比較有限的。自己的小程序還可以隨便寫點腳本簡單實現,但是公司的大項目呢?公司請你來是要你來解決問題而不是創造問題的。如果沒有完善的框架,依賴於一個框架然而那個框架出現了bug,那麼遭殃的可是整個公司啊。

4.資料多

Java這幾年的熱門程度這裡不必多說了,要找學習資料,從視頻到書籍到博客一搜一大堆,有點問題,隨便一搜Stackoverflow也很容易找到。

5.垃圾回收

在大學的時候寫過C++,記得那時候總是要自己分配內存,然後free掉。真是一項繁瑣的工作,而Java 的GC演算法部分做的真是完善。對於一般的工作使用者幾乎可以不用去理會垃圾的回收,GC演算法會自動回收那些不被使用的對象,釋放內存,這個是透明的存在。(當然,你得防止內存泄漏)

我喜歡把編程語言比喻成各種武器,例如Java是把沉重的大劍,Python是一把鋒利的匕首。我可以用大劍來砍殺一隻戰鬥力只有5的小兵,但是拿著卻那麼重,移動速度都降低了,而用匕首則可以快速解決簡單優雅。但是遇到大boss倘若沒有大劍,匕首當然可以只不過顯得更累。所以編程語言是不同的工具,沒有最好的,只有最合適的,具體情況具體分析。

---

寫Java寫了一兩年了,發現這是個可靠的語言,寫著寫著雖然啰唆,但是有IDEA這樣神一樣的IDE啊!當然Java還有非常多的優點,這裡就不展開說了。對於Java還有其他語言特別是其他語言還有非常多不熟悉的地方了,如果上文有不正確的地方,請指正哈。

2016-12-20 補充

發現很多人對Java到底是不是一門好語言爭議挺大的。個人經驗所說的也確實不一定是正確的,還是那句話沒有最好的語言只有最適合的。每個人的邏輯方式不一樣,所喜歡所欣賞的自然不可能完全一致。完全可以去詬病一門語言,但是當你把它如何的貶低之時可能要顧及下你當前的文字有多少的初學者在閱讀,他們正在猶豫選擇著他們人生的第一門語言。而他們有的僅僅只是剛入學的學生,但是更有的是馬上面臨工作想要努力學會一門語言先找個工作,畢竟沒有經濟來源興趣難以支撐。

我這裡只想說說自己的經歷,也當做給剛要面臨找工作人做參考:

我今年剛畢業於沿海某211大學,去年的這個時候也是經歷了校招,我們學校雖然不算省里最好的但是還算是過得去吧。然而招聘的崗位很多,但多的是Android、IOS、Java、PHP、前端。而被網上炒的很熱的python、go、scala在我們的校招提供的崗位幾乎等於0。當然區域、學校不同情況可能截然相反,這裡權當給個真實案例,不想特彆強調什麼。


沒人說下kotlin?(後文簡稱kt)

為什麼這麼方便的東西,現在還沒什麼人用?

無縫和java程序銜接,這點應該就免除了大部分人的顧慮了吧?

即使是以前的項目,在maven或gradle裡面加個配置,就能開寫kotlin了.

java和kotlin也都能互相調用,akka,spark等也都能用.

默認的immutable,但guava news出來的mutable也都能用.

豐富且嚴謹到恰到好處的語法糖,表達能力強但不啰嗦,極少的代碼冗餘.

很多人對kt沒有一個正確的定位,可能大家第一反應是拿它去和scala,groovy比較.從語法的角度而言,kt絕對不差(事實上比scala要略差一籌),但他們都是非常優秀的jvm語言,總體來說是難分伯仲的,kt也沒辦法將他們甩出一個身位.但我必須得說,絕大部分情況下(指常規開發),如果你選擇kt作為你的第二jvm語言,比用scala,groovy等,開發工程中的收益要多的多的多...(注意我不是單單在說語言層面了)

我簡單談談我自己的體會:

1.kt是靜態強類型,既然用java了,那肯定是需要這些特性的,不然我就用node.js做gateway,豈不美哉?

2.kotlin官網首頁重點強調的:100% interoperable with Java?. 這就決定了,從java切換到kt,相比其他語言,成本要少的多.比如我司有一套代碼生成器,當然,生成的是java代碼,那麼我用kotlin寫bussiness,其實不需要任何操心,repository拿過來就用,反之亦然,我用kotlin寫的lib,或者封裝了一些輔助方法的base類,java如果要用的話,同樣是拿過來即用,不像scala還需要做一層適配.事實上,java那邊根本感知不到其實他調用的是scala代碼.

3.基於2的優勢,kt推薦的是盡量少造輪子,時間處理還是用的joda,http還是Apache的HttpClient,也會糾結netty還是akka.大家回憶下自己學習語言的過程,是花在語法上的時間多,還是花在熟悉標準庫上的時間多?(python玩家這點應該感觸最深吧?),所以學習kt,真的就只有一個學習語法的成本.這點還會帶來一個優勢,就是kt項目,jar包的大小不會受多大的影響,這在android上更是一個大優勢.

4.最後,也是最重要的優勢:jetBrains爸爸全方位無死角超貼心的配套支持

(畫外音:用過Resharper,IDEA,WebStorm,PyChrome的朋友,讓我看到你們的雙手!!)

熟悉jetBrain的朋友,應該能夠感覺到,這是一家非常有個人特色和魅力的公司,其在ide和pl工程領域的積累,大家應該也心裡有數.

j系ide都有一個特點,就是對開發者極其友好,基於語法(AST?)而非文本的代碼分析,帶來的超高的智能提示準確率和極度便利的重構,對可能的異常代碼的警告和解決方案的提示.jetBrains總是傾向於讓開發者寫出嚴謹又簡潔且魯棒的代碼,幸運的是,kt也繼承了爸爸的這些特質,不僅僅是語法的嚴謹,還體現在了開發過程中,比如maven配置,java交互,nullable的註解提示,idea配套插件.

說了這麼多文字,且廢話占多數,想必大家也有點煩了,那我下面就以java和kt的比較為切入點,介紹一些kt的特點吧.

其實就是過一遍Kotlin/kotlin-koans,建議有興趣的可以clone個玩玩

val 定義的變數不可變

var 同C#,val和var都是隱式強類型推斷,

val的作用在於,我前面定義一個orderState,即表示,這個變數就只做取值用途,你別再拿來干別的事情了,避免了一值多用的bad small.

默認參數,及參數名傳參:

減少無意義多態的使用,但又比js的一值多用直觀的多.

labmda寫法改進

必須在一個花括弧中,如果以lambda為參數,可省略(),看起來很舒服

如果只有一個參數,則可以省略聲明,用it代替,(同scala的 _ )

scala的一個參數對應的_只能出現一次,第二個_代表第二個參數,更簡潔的寫法但帶來更模糊的語義,孰優孰劣不談,但兩者的風格差異在這個細節中可見一斑.

nullable

kt對null pointer 問題非常敏感,任何可能的npr都需要顯示的處理,如果不處理,nullable會一直往後傳染並給出一個警告,這時候你要麼用!!表示我tm確定這裡肯定不為null,要麼用?:表示如果為空,則表達式的值為後面提供的預設值.

items是訂單詳情的集合,詳情包含ActualWeight,但未發生實提則為null,如果是傳統寫法,需要先判斷find出來的是否是null,再判斷這個find是否有實提,如果有則返回實提,沒有則返回double 0(lambda可省略return)

大家可以體會下這省了多少工夫?

擴展方法,

這是我覺得所有靜態語言都應該提供的特性,原理非常簡單,但帶來的寫法上的優化非常有價值

表示所有的string實例,在lastChar的可訪問範圍內,都多了一個成員函數,其實就是

static StringHelper.lastChar(str:String)

的變種,在不支持em的java看來,就長剛才這個樣子,用起來就是如下形式:

H3.em3(H2.em2(Helper1.em1(what)))

low爆了是吧? 下面是我封裝的一個操作poi的代碼

next是移動到下一單元格,設置樣式是複製第一行的樣式,其實還可以封裝,為了顯示錶達出我的操作意圖,這裡就留著了.

模式匹配,抄的scala 不多說

scala的方法只有一個參數的時候,可以省掉()

kotlin 如果沒有參數,需要設置成屬性,然後聲明get方法,如果是一個參數,必須對這個方法進行顯示的infix聲明 , 兩者dsl的能力並未差太多,但kt更嚴謹,這裡又是兩者風格的差異的體現.

最後修一下kt的dsl能力

我覺得說到這裡就差不多了,再說也就是抄襲koans,也沒啥大的意思了,最後扯點其他的作收尾吧.

談一談我對語法糖的看法:

我個人對語法糖的評價是非常高的,也許這個糖的原理並不複雜,比如我之前提到的擴展方法,但我們認識它的原理是遠遠不夠的,還得理解,為什麼要有這玩意.

H3.em3(H2.em2(Helper1.em1(what))) VS what.em1().em2().em3()

上面後者省略了import H1 H2 H3的過程,綜合起來工作量也沒差太多

但真的就僅僅是寫法和視覺上的優化嗎?其實遠遠不止.

前者是,XXHelper裡面有個方法,可以操作what這個類型並做一些事情,

後者是,what既然有這樣的特質,那麼它就應該擁有某樣的能力

前者是思維先找到Helper,再找到具體方法,

後者是what自然而然的提示出,what就有這樣的能力(ide的智能提示),我敲下what+ . 後,它的能力就展示在我面前了,而不需要我還去翻箱倒櫃的找helper方法.

第二談談激進技術的風險和收益,與不同階層人員關注點的不同.

我最早推進kt的時候,最大的阻力就是來自公司的架構師團隊,他們的關注點是:

kt相比java,能提高性能嗎?(不能,kt的性能與java極度接近但略小與java)

kt解決了什麼java並不能解決的問題嗎?(沒有,kt只是讓你更快更好的寫java代碼)

kt能減少項目bug,提高項目穩定性嗎?(某種程度上來說有一定幫助,但更多是看人,這理由也不夠強力)

那時候我只是個開發 leader,這種層面肯定是沒有太大的決策權的,他們的想法我也理解,公司幾百號開發,提高一點點效率,相比引入新技術棧的風險,肯定是穩定壓倒一切啦.

後面在我的爭取下,我在一個簡單項目上少量使用kotlin作為試點,後來一些原因我離開了這個公司,去小公司當技術合伙人去了,現在那段kotlin代碼應該還跑在公司的tomcat上面,其他人不去翻代碼,他們也不會知道這是用kotlin寫的吧?

現在我雖然是公司的技術負責人了,但小公司嘛,代碼還得寫,測試,發布,部署cdn 配置jenkins也都得上.我仍然工作在一線.所以我再站在一線崗位發表下看法.

福利的定義有很多,優渥的薪水,高配置設備,彈性工作時間這個大家都知道了.

但其實還有一種福利,很多人忽略了.就是讓碼農更舒服,更自由的寫代碼,我覺得,如果有人提出他希望以某種方式更好的寫代碼,只要有足夠充分的理由說服我,我們可以一起研究這種方案的風險與收益,在公司可以承擔的範圍內,都願意去優化,去採用,哪怕其實一番折騰下來,收益都被折騰抵掉了,也是可以的,這算是公司給員工的福利的一部分.


Idris

Dependent Type

幾乎沒有語言支持這個

你可以通過構造形式證明保證你寫的程序一定是對的,永遠不會有 bug。

// node 主要是庫做的好,話說我要不要把 libuv 和 idris 搞一起呢?


MATLAB, 用過了很多科學計算相關的語言, 比如R, Python, Mathematica等, 感覺還是MATLAB最適合我。

亮點一: 功能一站式。 比如R, Python, 不同功能要用不同的包, 這些包的作者不同, 代碼質量參差不齊, 風格各異, 學習成本較高, 還要自己識別優劣。

亮點二: 大部分函數是開源的, 可以根據自己需要改寫或加速,這點Mathematica做不到。

亮點三: IDE強大,針對數據分析的。

亮點四: 矩陣運算語法簡潔,速度快。可以很方便從數學公式轉化為代碼。

亮點五: 大部分代碼可以自動轉化為C/C++

其他語言沒有包含所有三個亮點的。


用haskell求周長為21,邊長為整數的所有三角形

[(a,b,c) | c&<-[1..10], b&<-[1..c], a&<-[1..b], a&

結果如下:

[(7,7,7),(6,7,8),(5,8,8),(6,6,9),(5,7,9),(4,8,9),(3,9,9),(5,6,10),(4,7,10),(3,8,10),(2,9,10),(1,10,10)]


推薦閱讀:

PHP7 出來之後,HHVM 還有什麼優勢嗎?
swoole啟動2萬個定時器對性能有影響嗎?
現在國內中小型的IT行業的公司,asp.net和php哪個應用得比較普遍?
如何編寫不可維護的php代碼?
「Facebook 開發的高性能PHP虛擬機 HHVM 比官方的 PHP解釋器 快超過9倍」的說法是否屬實?

TAG:JavaScript | Python | PHP | Nodejs |