はじめに
初めまして。
現在、未経験からのWebエンジニア転職を目指し、プログラミングスクールにて学習中の だい と言います。
RubyやRuby on Railsを中心とした学習を進める中でProcという用語に出会い、いまいち理解できないまま今に至っているので、この記事では振り返りも兼ねてまとめてみようと思います。
初学者のため、記事の内容に誤りがある可能性がございます。
誤った内容を記述している箇所がございましたら、コメント等でご教示いただけると幸いです。
Procって何?
早速、本題の「Procとは何者なのか?」に切り込んでいこうと思います。
公式のリファレンスマニュアルを見ると、ProcとはRubyがデフォルトで用意しているクラスの一つのようです。以下のように説明されています。
ブロックをコンテキスト(ローカル変数のスコープやスタックフレーム)とともにオブジェクト化した手続きオブジェクトです。
なんだか難しいですが、以下2つに分けて考えてみようと思います。
- ブロックをオブジェクト化したもの
- (ブロックを)コンテキストとともにオブジェクト化したもの
それぞれについて見ていきましょう。
1. ブロックをオブジェクト化したもの
ブロックとは、一連の処理をまとめたカタマリを指します。do~end
または{}
で囲まれたアレですね。
# do~endで書く場合
[1, 2, 3].each do |n|
puts n * 2
end
# {}で書く場合
[1, 2, 3].each { |n| puts n * 2 }
このブロック内の処理をまとめてオブジェクトとして扱おう!と作られたものが、Procオブジェクトというわけです。
Procオブジェクトは、「Procクラスから生成されるインスタンス」を指します。
Procオブジェクトに変換することで、複数の箇所で使いたいブロックの処理を変数に代入し、好きな場所で引数として呼び出すことができるようになります。
# Procオブジェクトを用いない場合 (その都度ブロックを記述する)
[1, 2, 3].each { |n| puts n * 2 }
[4, 5, 6].each { |n| puts n * 2 }
# Procオブジェクトを用いる場合 (変数に代入して複数箇所で使える)
proc = Proc.new { |n| puts n * 2 }
[1, 2, 3].each(&proc)
[4, 5, 6].each(&proc)
※ Procオブジェクトの作り方、呼び出し方については後述します。
通常のブロックでは記述されたその場でしか処理を実行できないため、この点はProcの大きな特徴といえます。
2. (ブロックを)コンテキストとともにオブジェクト化したもの
コンテキストとともにオブジェクト化、と言われてもよくわからないので、まずは簡単な例を見てみましょう。
message = "おはようございます"
greeter = Proc.new { puts message }
greeter.call # => "おはようございます"
※ Procオブジェクトの作り方、呼び出し方については後述します。。。
上記の例では、greeter = Proc.new { puts message }
でProcオブジェクトが生成される前に、ローカル変数message
が定義されていますね。
Procオブジェクトは、自身が生成される時点で存在する外部の変数(上記例の場合はmessage
)を記憶し、その参照先の値を扱うことができます。
そのため、greeter.call
でProcオブジェクトを呼び出すと、変数message
に代入された文字列"おはようございます"
が取り出されるというわけです。
ちなみに、Procオブジェクト生成後に変数の値を再代入すると、変更後の値が取り出されます。
message = "おはようございます"
greeter = Proc.new { puts message }
message = "こんにちは"
greeter.call # => "こんにちは"
※ Procオブジェクトの作り方、呼び出し方については後述します。。。。。
これは、Procオブジェクトが記憶しているものが変数に代入された値自体ではなく、変数から値への参照であるためです。
参照については、こちらの記事がわかりやすかったです。
ローカル変数のスコープ(コンテキスト)を記憶しつつブロック内の処理をオブジェクト化するため、「コンテキストとともにオブジェクト化」と説明されていたのですね。
Procオブジェクトの使い方
ここでは、上の例でも少し登場した、Procオブジェクトの作成および実行の仕方について見ていきたいと思います。
実行する
作成したProcオブジェクトに対してcall
メソッドを呼び出すことで、Procオブジェクトを呼び出して処理を実行できます。
作成する
作成の方法は、以下のとおり4種類あります。
1. Proc.new
で作る
proc_a = Proc.new { |n| n * 4 }
puts my_proc.call(2) #=> 8
お馴染みのnew
メソッドの引数にブロックの処理を渡すことで、Procクラスのインスタンスを作成しています。
2. proc
メソッドでブロックをProcオブジェクトに変換
proc_b = proc { |n| n * 4 }
puts my_proc.call(2) #=> 8
ブロックをProcオブジェクトに変換するproc
メソッドを用いて、Procクラスのインスタンスを作成しています。
3. メソッドの引数として渡す
def proc_generate(&proc_c)
puts proc_c.call(2) #=> 8
end
proc_generate { |n| n * 4 }
proc_generate
というメソッドの引数としてブロック({ |n| n * 4 }
)を受け取り、Procオブジェクトを作成しています。
メソッドの仮引数の先頭に&
をつけることで、呼び出し元から渡されるブロックがProcオブジェクトに変換されます。
メソッド内では、生成されたProcオブジェクトに対してcall
メソッドを呼び出すことで、処理を実行しています。
4. lambda
を用いて作成する
こちらについては後述します。
lambda
(ラムダ)って何?
Procが何者か少しずつわかってきたところで、またよくわからない用語が出てきました…
lambda
とは、Procオブジェクトを作る別の方法を指します。
(正しい定義は、「Rubyで無名関数を作るためのひとつの方法」です。)
lambda
を使ったProcオブジェクトの作成方法を見てみましょう。
#lambdaを使ってProcオブジェクトを生成する
my_proc = lambda { puts 'Proc' }
# ->構文でもlambdaと同様にProcオブジェクトを生成できる
# my_proc = -> { puts 'Proc' }
#Procオブジェクトの実行
my_proc.call
#実行結果
Proc
lambda
によって作成されたProcオブジェクトは、Proc.new
などその他の方法で作成されたオブジェクトと挙動が異なる点があります。
一例として、lambda
によって作成されたProcオブジェクトの方が、引数の数が厳密に判定されます。
-
Proc.new
の場合
実行時に渡される引数の数が合わなくても、過不足分は無視して実行されるmy_proc = Proc.new { |a, b| a + b } my_proc.call(1, 3) # => 4 my_proc.call(1) # => 1 my_proc.call(1, 3, 5) # => 4
-
lambda
の場合
実行時に渡す引数の数は厳密に判定され、過不足がある場合はエラーが発生するmy_lambda = lambda { |a, b| a + b } my_lambda.call(1, 3) # => 4 my_lambda.call(1) # => wrong number of arguments (given 1, expected 2) (ArgumentError) my_lambda.call(1, 3, 5) # => wrong number of arguments (given 3, expected 2) (ArgumentError)
この辺りは、こちらの記事で詳しく解説されていたので、詳しく知りたい方はぜひ確認してみてください。
まとめ
Procオブジェクトとは、
- ブロックをコンテキストとともにオブジェクト化したもの
- ブロック内の処理を変数に格納し、複数の箇所で呼び出せる
- 呼び出す際は
call
メソッドを使う
実際にProcオブジェクトを扱ったことがないため教科書的な説明に留まりましたが、開発の場面で遭遇した際にまた理解を深められるよう、学習を進めていこうと思います。
最後まで読んでいただき、ありがとうございました!
参考にした記事
RubyのProcとは?ブロック・Proc・lamdaの違いをマスター
【Ruby】Procオブジェクトについて整理する
rubyのProcって何をしてるの
【Ruby】「オブジェクトへの参照」と「変数の中身」