大家好,我是煎魚。
之前在《Go 語言設計哲學》電子書中分享了《為什麼 Go 不支援函式過載和預設引數?》的思考和原因。最近有一位從其他程式語言轉型 Go 的同學提出瞭如下靈魂拷問。
“為什麼 Go 不能像 PHP、Python 一樣,在呼叫函式時,直接帶上引數名和值一起傳入。這樣就不用特意去看這個函式的形參的命名、型別等。明明 PHP8 都支援了?”
今天針對命名引數這個特性展開思考,看看 Go 怎麼回事。
命名引數
如果有了命名引數這個功能特性,在我們呼叫函式/方法時,傳入函式的引數不需要固定位置,位置可以隨意調整,名字對就行。甚至有的工具會基於此,做自動化的文件等自描述的場景。
PHP8 的例子:
function hello(string $name, int $age) {
echo $name, $age;
}
// 兩次呼叫的引數位置不一樣
hello(name:'煎魚', age:18);
hello(age:18, name:'煎魚');
理想中 Go 的例子:
package main
func sum(a int, b int) int {
return a + b
}
func main() {
resp := sum(a=7, b=28)
println(resp)
}
由於不支援,執行編譯就會報錯:
./prog.go:8:15: syntax error: unexpected = in argument list; possibly missing comma or )
Go 必須是如下程式碼:
func sum(a int, b int) int {
return a + b
}
func main() {
resp := sum(7, 28)
println(resp) // 輸出結果:35
}
也就是按函式所宣告的引數位置傳入,才能執行成功。
設計哲學
Go 語言在錯誤處理、函式過載以及預設引數等社群議題討論時,總會祭出其的設計理念是:“顯式大於隱喻”,追求明確,顯式,要不就是 “less is more”。
每次看到只要不滿足這個理念的提案、討論,基本 Go 團隊可以圍繞這個論據給出一堆理由後拒絕掉。
本文提到的帶命名引數傳入函式,看起來非常顯式,很明確了。似乎很符合 Go 的設計哲學理念,感覺不應該沒有才對?
社群思考
在 golang-nuts 郵件群組的多年討論中,涉及到以下幾類論據作為支撐:
- 這是一個語言設計和可讀性問題,“命名引數” 和 “預設引數” 基本是成配套出現在語言設計中,需要一併考量合適與否。會出現一加就相當於要引入許多新語法特性了,能玩出騷操作。
- 引入這類特性會給 Go 帶來新語法複雜度,如果函式引數名修改了,那是不是破壞相容性?是不是呼叫方全都得改一遍?如果出現同名的引數名,誰先誰後?怎麼覆蓋?過長的話,函式呼叫會不會過於難接受?組合結構體覆蓋方法時,方法引數名需不需要保持一致?會產生一大堆新問題。
- 引入後會產生大量的函式可選引數(命名引數+預設引數),原本只需要知道函式形參是什麼,結果引入後需要檢視名字、預設值以及對應的預設邏輯等,會加大程式設計師心智負擔。
- 編譯器本身不需要關注這些資訊,為這個特性加大編譯器的各項開銷是不必要的,沒有理由讓編譯器在編譯程式碼中儲存函式的引數名稱(需要具體考究深意)。
我們在討論中也有提到,這個特性可以藉助 go:generate
的特性來實現類似的功能,有興趣的朋友可以看看 go-named-params 這個開源庫。
顯然官方態度是,增加命名引數特性的弊大於利,貿然增加會影響到 Go 本身標榜的優勢(簡潔)。認為大可不必加,工具的問題需要讓工具自己解決。
總結
在這篇文章中,我們針對其他程式語言既有的 “命名引數” 特性進行了分析和說明。顯然 Go 團隊在討論中,認為該項特性對於靜態語言,尤其對於 Go 團隊來講,似乎好處太少,加了會影響自己的風格(less is more),還可能會影響效能,真是大可不必。
各語言間的功能特性對比,是個老大難的問題。如果都一樣,那豈不是搞個大單體程式語言算了?這顯然是不現實的。
文章持續更新,可以微信搜【腦子進煎魚了】閱讀,本文 GitHub github.com/eddycjy/blog 已收錄,學習 Go 語言可以看 Go 學習地圖和路線,歡迎 Star 催更。