トラック
/
Crystal
Crystal
/
シラバス
/
`Proc`とブロック
`P

`Proc`とブロック の Crystal

1個の演習

`Proc`とブロックについて

ブロックとは、メソッドに渡すことができるコードのまとまりです。 他のプログラミング言語では、これは無名関数やラムダと呼ばれます。 メソッドは、ブロックをコードのまとまりを表すProcに変換できます。

def my_method(&block)
  1 + yield
end

my_method { 2 }
# => 3

ブロックとProcは、繰り返し処理やコールバックなど、Crystalで非常によく使われます。 また、DSL(ドメイン固有言語)を作るのにも使われます。DSLの例としては、WebフレームワークのKemalや、テストフレームワークのSpec(Crystalの解答をテストするのに使われるものです)があります。

ブロック

ブロックを受け取るメソッドを作るには、最後の引数の前にアンパサンド(&)を付けます。 これにより、ブロックがProcに変換され、その引数に代入されます。 あとはyieldを使うと、メソッドに渡されたブロックが実行されます。

def my_method(&block)
  1 + yield
end

ブロックを渡す方法は2つあり、do ... endキーワードを使うか、波括弧({})を使うかのどちらかです。 主な違いは優先順位です。do ... endは複数行のブロックを書けますが、波括弧({})は書けません。

my_method do
  2
end
# => 3

my_method { 2 }
# => 3

yield

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

他の明示的な型と同様に、コンパイラーは型が期待どおりであることを強制します。 ブロックがメソッドに指定された型と一致しない場合は、エラーが発生します。

省略記法

引数を1つだけ取り、その引数に対してメソッドを1つだけ呼び出すブロックには、省略記法があります。 |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)という構文を使います。 最後の型は常に戻り値の型で、それ以外は引数の型です。 つまり、型が1つだけの場合はそれが戻り値の型となり、その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`とブロックを学習する

練習はロックされています

`Proc`とブロックを練習するには、あと1個の演習のロックを解除してください