軌道
/
Crystal
Crystal
/
課程大綱
/
Proc 與區塊
Pr

Proc 與區塊 在 Crystal

1 個練習

關於 Proc 與區塊

區塊 是一段可以傳遞給方法的程式碼。在其他程式語言中,這被稱為匿名函式或 lambda。方法可以將區塊轉換成 Proc,而 Proc 代表一段程式碼。

def my_method(&block)
  1 + yield
end

my_method { 2 }
# => 3

區塊和 Proc 在 Crystal 中非常常用,例如用於疊代和回呼。它們也用來建立 DSL(領域特定語言),常見的 DSL 例子有網頁框架 Kemal 或測試框架 Spec(就是用來測試你的 Crystal 解答的工具)。

區塊

要建立一個能接收區塊的方法,你需要在最後一個引數前加上一個 & 符號(&)。這會把區塊轉換成 Proc 並指定給該引數。接著使用yield就會執行傳給方法的區塊。

def my_method(&block)
  1 + yield
end

傳遞區塊有兩種方式:使用 do ... end 關鍵字,或使用大括號({})。兩者最主要的差別在於優先順序,do ... end 支援多行區塊,而大括號({})不支援。

my_method do
  2
end
# => 3

my_method { 2 }
# => 3

Yield

yield 是一個關鍵字,用來執行傳給方法的區塊。這表示如果沒有呼叫yield,區塊就永遠不會被執行。它也可以接收引數,這些引數會傳給區塊。可以傳給區塊的引數數量沒有限制。要在區塊中使用這些引數,你可以使用 |x, y, ...| 語法。其中 x、y、... 是引數的名稱。

def my_method(&block)
  1 + yield 2
end

# With curly braces:

my_method { |x| x * 3 }
# => 7

# With do ... end:

my_method do |x|
  x * 3
end
# => 7

在上面的例子中,區塊收到引數 2,將它乘以 3 並回傳 6。接著方法把 1 加到結果上,回傳 7。

如果傳給yield的引數數量多於區塊預期的引數數量,就會引發錯誤。

def my_method(&block)
  1 + yield
end

my_method { |x| x * 3 }
# Error: too many block parameters (given 1, expected maximum 0)

區塊的型別

區塊可以明確或隱含地撰寫。隱含區塊最為常見,上面例子中的就是這種。你甚至可以給區塊不同型別的引數,編譯器會推斷型別並建立聯合型別。

def my_method(&block)
  yield "a"
  yield 1
end

my_method do |x|
  p typeof(x)
end

# output: (String | Int32)

撰寫明確區塊時,你可以指定引數的型別以及回傳型別。做法是使用 Arg1Type, Arg2Type, ... -> ResultType 語法。其中引數以逗號(,)分隔,回傳型別則以 -> 分隔。

def my_method(&block : Int32, Int32 -> Int32)
  1 + yield(2, 3)
end

my_method { |x, y| x * y }
# => 7

與其他明確型別一樣,編譯器會強制型別符合預期。如果區塊與傳給方法的型別不符,就會引發錯誤。

簡寫

對於只接收一個引數、且只對它呼叫一個方法的區塊,有一個簡寫。你可以不用 |x|,改在引數名稱前加上 &。

def my_method(&block)
  yield "a"
end

# This is: 

my_method { |x| x.upcase }
# => "A"

# Is the same as:

my_method &.upcase
# => "A"

Proc

Proc 與區塊有非常緊密的關聯。關鍵差異在於 Proc 是一種型別,而區塊不是。Proc 代表一個函式指標,可以像變數一樣四處傳遞。

要定義 Proc,你要使用 proc 實字 ->(args) { ... } 語法。引數以逗號(,)分隔,區塊則以 { ... } 分隔。要呼叫 Proc,你使用 call 方法並把引數傳給它。參數的型別是必要的。回傳型別會從 proc 的主體推斷出來,但也可以明確提供。

my_proc = ->(x : Int32, y : Int32) : Int32 { x * y }
my_proc.call(2, 3)
# => 6

擷取區塊

擷取區塊是一種從區塊建立 Proc 的方式。做法是在方法的最後一個引數前使用 & 運算子。這會把區塊轉換成 Proc 並指定給該引數。

def my_method(&block)
  block.call
end

my_method { 1 + 2 }
# => 3

將 proc 傳給方法

Proc 可以傳給預期接收區塊的方法。當你想依某些條件傳入不同的區塊時,這很方便。

def my_method(&block)
  yield 5
end

my_proc = -> (x) { x * 2 }
my_method(&my_proc)
# => 10

宣告變數的型別

宣告變數時,你可以使用 : Type 語法來宣告變數的型別。對 proc 來說,你要使用 Proc((ArgType, ...), ResultType) 語法。最後一個型別一律是回傳型別,其餘的則是引數型別。也就是說,如果只有一個型別,它就是回傳型別,而該 proc 不接收任何引數。

my_proc : Proc(Int32, Int32, Int32) = ->(x, y) { x * y }
other_proc : Proc(Int32) = -> { 1 }

從方法宣告 proc

你也可以從方法宣告 proc。

def my_method(a, b)
  a + b
end

my_proc = ->my_method(Int32, Int32)
my_proc.call(2, 3)
透過 GitHub 編輯 連結會在新視窗或分頁中開啟

學習 Proc 與區塊

練習已鎖定

再解鎖 1 個練習,就能練習 Proc 與區塊