to_proc
Ruby 的 to_proc
Ruby 的區塊非常強大。您可以輕鬆地將數字陣列轉換為字串
[1, 2, 3].map { |n| n.to_s } #=> ["1", "2", "3"]
當然,您也可以使用這個捷徑來做到這一點
[1, 2, 3].map &:to_s #=> ["1", "2", "3"]
&
運算子會將物件轉換為適合作為區塊傳遞的 Proc。您可以透過實作 to_proc
方法,讓任何類別回應此運算子。Symbol 有一個 to_proc 方法。
這一切都很好,但是如果您想將參數傳遞給方法,該怎麼辦。例如
[10, 20, 30].map { |n| n.modulo(3) } #=> [1, 2, 0]
我們可以寫一些像 &:modulo(3)
這樣的東西來使其工作嗎?結果 不行,至少 沒有那麼容易。
不僅如此,由於 Ruby 必須將 Symbol 轉換為 Proc,因此與使用普通區塊相比,效能會略微下降。
最後,Ruby 的 Symbol#to_proc 實作具有 Proc 的快取,因此每次使用相同的符號時都不會建立它們,但仍然比普通的區塊慢一點。
Crystal 的 to_proc?
起初我們考慮讓 Crystal 具有相同的語法,但是有點 hacky:如果您執行 &:to_s
,由於 &
的參數是 Symbol,我們可以重寫原始碼以接收一個區塊
# This:
[1, 2, 3].map &:to_s
# is rewritten to this:
[1, 2, 3].map { |x| x.to_s }
對於其他參數,我們會做一些不同的事情(例如將函式類型轉換為區塊)。
幸運的是,waj 提出了更好的建議:如果我們寫成 &.to_s
呢?
[1, 2, 3].map &.to_s
現在,這是一種新的語法,與 Ruby 不同。如果您在 Ruby 中執行此操作...
irb(main):001:0> [1, 2, 3].map &.to_s SyntaxError: (irb):1: syntax error, unexpected '.' [1, 2, 3].map &.to_s ^
這表示在 &
之後放置一個點在 Ruby 中沒有任何意義,這也表示此語法可用於賦予它新的含義。因此,在 Crystal 中,我們選擇改用此語法。
透過這個小小的更改,我們可以很容易地將參數傳遞給方法
[10, 20, 30].map &.modulo(3) #=> [1, 2, 0] ... but only in Crystal ;-)
不僅如此,您還可以寫這個
[1, 20, 300].map &.to_s.size #=> 1, 2, 3
或這個
[[1, -2], [-3, -4]].map(&.map(&.abs)) #=> [[1, 2], [3, 4]]
當然還有這個
[1, 2, 3, 4].map &.**(2) #=> [1, 4, 9, 16]
最好的是,這只是一個語法重寫,沒有任何效能損失。