11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

RubyのProcとは何者なのか?

Last updated at Posted at 2023-10-21

はじめに

初めまして。

現在、未経験からのWebエンジニア転職を目指し、プログラミングスクールにて学習中の    だい と言います。
RubyやRuby on Railsを中心とした学習を進める中でProcという用語に出会い、いまいち理解できないまま今に至っているので、この記事では振り返りも兼ねてまとめてみようと思います。

初学者のため、記事の内容に誤りがある可能性がございます。
誤った内容を記述している箇所がございましたら、コメント等でご教示いただけると幸いです。

Procって何?

早速、本題の「Procとは何者なのか?」に切り込んでいこうと思います。

公式のリファレンスマニュアルを見ると、ProcとはRubyがデフォルトで用意しているクラスの一つのようです。以下のように説明されています。

ブロックをコンテキスト(ローカル変数のスコープやスタックフレーム)とともにオブジェクト化した手続きオブジェクトです。

Ruby 3.2 リファレンスマニュアル

なんだか難しいですが、以下2つに分けて考えてみようと思います。

  1. ブロックをオブジェクト化したもの
  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オブジェクトが記憶しているものが変数に代入された値自体ではなく、変数から値への参照であるためです。

【参照のイメージ】
Image from Gyazo

参照については、こちらの記事がわかりやすかったです。

ローカル変数のスコープ(コンテキスト)を記憶しつつブロック内の処理をオブジェクト化するため、「コンテキストとともにオブジェクト化」と説明されていたのですね。

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】「オブジェクトへの参照」と「変数の中身」

11
6
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?