Go 想要加個箭頭語法,這下更像 PHP 了!

煎魚發表於2022-06-09

大家好,我是煎魚。

在六一兒童節前夕在摸煎魚時,看到一個很神奇的 Go2 的技術提案,想要加一個更簡單、更輕量的匿名函式語法。

今天就由煎魚和大家一起看看。

新提案

新的 Go 提案目的是新增輕量級的匿名函式語法,業內別名又叫 “箭頭語法”,是由 @Damien Neil 所提出的,提案的來源是《proposal: Go 2: Lightweight anonymous function syntax》,褒貶都有:

我們由此進行展開。

如下例子:

import (
    "fmt"
    "math"
)

func compute(fn func(float64, float64) float64) float64 {
    return fn(3, 4)
}

func main() {
    hypot := func(x, y float64) float64 {
        return math.Sqrt(x*x + y*y)
    }
    fmt.Println(hypot(5, 12))

    fmt.Println(compute(hypot))
    fmt.Println(compute(math.Pow))
}

上述程式碼主要是實現了多個匿名的閉包函式,實際上業務邏輯沒有什麼。認為由於閉包簽名繁雜,導致程式碼可讀性不高。

為了避免這種情況,許多語言允許省略匿名函式的引數和返回型別,因為它們可能是從上下文派生的,能夠直接被複用。

如下 Scala 的例子:

compute((x: Double, y: Double) => x + y)
compute((x, y) => x + y) // Parameter types elided.
compute(_ + _) // Or even shorter.

Rust 的例子:

compute(|x: f64, y: f64| -> f64 { x + y })
compute(|x, y| { x + y }) // Parameter and return types elided.

因此這個 Go 提案就是希望針對匿名閉包增加這個輕量級的語法,讓程式碼看起來更加的簡潔,讓程式碼可讀性提高。

PHP 的例子:

$x = 1;
$fn = fn() => $x++; // 不會影響 x 的值
$fn();
var_export($x);  // 輸出 1

更有那味了。

真實案例

Cap'n Proto

Go 開源庫 Cap'n Proto(capnproto/go-capnproto2)是一種極其快速的資料交換格式,類似於Protocol Buffers,但速度快得多。

以下是其程式碼使用片段:

s.Write(ctx, func(p hashes.Hash_write_Params) error {
  err := p.SetData([]byte("Hello, "))
  return err
})

假設我們是 Rust,效果如下::

s.Write(ctx, |p| {
  err := p.SetData([]byte("Hello, "))
  return err
})

errgroup

這個 errgroup 庫相信大家不會陌生,常用於多個 goroutine 的非同步場景中的 err 處理和同步。

以下是其使用片段:

g.Go(func() error {
  // perform work
  return nil
})

假設我們是 Scala,效果如下:

g.Go(() => {
  // perform work
  return nil
})

只從程式碼數量來對比看,確實是簡潔一些。

討論

這個提案引起了社群不小的轟動和討論,有多種不同的觀點。

語法格式

先從 Go 的語法角度來看。語法格式為:

[ Identifier ] | "(" IdentifierList ")" "=>" ExpressionList

例子會變成:

s.Write(ctx, p => p.SetData([]byte("Hello, "))

g.Go(=> nil)

更更更短了。

降低了可讀性

許多小夥伴認為這反而降低了程式碼可讀性,更難懂了,還得在腦子裡轉換幾道,才能知道是什麼意思...

你想想,隨便在公司上抓一隻煎魚。假設他沒有提前瞭解過這個語法,他能讀得懂這段程式碼是什麼意思嗎?

如下:

g.Go(=> nil)

顯然,他沒法 100% 確定。但沒有這語法時,只是正常的匿名閉包,是可以讀懂的。因為語法基本是通識,而箭頭語法並不是。

早期設計被拒絕

在 Go 早期的設計,其實對 “箭頭語法”,也就是本提案進行過研究。

當時的語法是:

func f (x int) -> float32

因為它不能很好地處理多個(非元組)返回值; 一旦出現 func 和引數,箭頭就多餘了,會變得很複雜。

雖然這麼做會看起來更 “漂亮”,但 “漂亮”(就像在數學上看起來一樣)可能仍然是是多餘的。它看起來也像是屬於一種“不同”語言的語法。

官方也認為必須非常小心,不要為閉包建立特殊語法。因為現在 Go 所擁有的是簡單而規律的語法和邏輯。

最終放棄了新增箭頭語法的想法。

用省略符替代

從程式碼示例來看,引起繁雜的主要是型別宣告和結構。因此也有人提出使用省略符來實現類似效果。

如下程式碼:

s.Write(ctx, func(p _) _ { return p.SetData([]byte("Hello, ")) })

這樣的好處是不需要語法改變。

總結

原提案作者的本意,可能是需要讓匿名閉包更加的簡潔,降低程式碼複雜度。但其實這本質上,節約的只是明面上的複雜度。

一旦引入這類 “箭頭” 語法,可能會更大的加劇腦子轉換的開銷。看程式碼時,得想想對對,會加重底下的腦力開銷。

當然,說不定我也是錯的。你覺得呢?是否支援 Go 新增輕量級的匿名閉包語法,也就是業內俗稱的 “箭頭” 語法。

歡迎大家在評論區留言和交流。

文章持續更新,可以微信搜【腦子進煎魚了】閱讀,本文 GitHub github.com/eddycjy/blog 已收錄,學習 Go 語言可以看 Go 學習地圖和路線,歡迎 Star 催更。

Go 圖書系列

相關文章