LoginSignup
2
2
記事投稿キャンペーン 「2024年!初アウトプットをしよう」

【Ruby】Procの基礎を理解する

Last updated at Posted at 2024-01-20

Procクラス

  • ブロックをオブジェクトとして扱いたい時に使用するクラス
    • ブロック
      • メソッドの引数として渡すことが出来る処理のまとまり
      • do .. end, { .. }
  • Procは、クロージャに相当する
i = 100
pr = Proc.new { i += 1000 } # proc { i += 1000 } も同様

p pr.call #=> 1100
p i #=> 1100

p pr.call #=> 2100
p i #=> 2100

クロージャとは

手続きをオブジェクトとして扱う機能があります。このオブジェクト化された手続きのことをクロージャと呼びます。

・クロージャとは、関数内で特定の変数を参照し続けることで、関数が状態を持てる仕組みのこと。
・クロージャは呼び出す度に、新たな関数を定義する関数ファクトリ。
・クロージャを使用することで、グローバル汚染を防ぐことができ、保守性が高まる。

クロージャとは関数とその関数が定義されている状態をセットにしたもの
・記述の省略が可能
・処理をまとめられる
・カプセル化ができる

クロージャのイメージ
def add_num n
  yield + n
end

i = 10

p add_num(1) { i += 100 } #=> 111
p i #=> 110

p add_num(1) { i += 1000 } #=> 1111
p i #=> 1110

上の例だと、

  • #add_num の引数に 1 を渡す
  • #add_num 内の yield がブロックの処理を呼び出す
  • ブロックの中で、ローカル変数 i を更新する
  • 更新された変数 in の加算を行っている(代入はしていない)
  • ブロック付きメソッドを呼び出す度に、外部の変数である i が更新されている

外部のローカル変数を捉え、その変数に対してアクセス・変更が可能

遅延評価したい場面などで使いやすい

ブロックをProcに変換して受け取りたい場合

def calc_result(x, &block)
  # p block.class #=> Proc
  result = block.call(x)
  puts "Result: #{result}"
end

i = 100

# 加算のブロックを渡す
calc_result(900) { |n| i += n } #=> Result: 1000

# 減算のブロックを渡す
calc_result(300) { |n| i -= n  } #=> Result: 700

p i #=> 700

最後の引数に「&」を付けることで、ブロックをProcオブジェクトとして受け取る

#block_given?

メソッドにブロックが与えられていれば真を返します。

def calc_result(x, y, proc_object = nil, &block)
  if proc_object
    result = proc_object.call(x, y)
    puts "Using Proc: #{result}"
  elsif block_given?
    result = yield(x, y)
    puts "Using Block: #{result}"
  else
    puts "No Proc or Block specified."
  end
end

# Procオブジェクトを定義
addition = Proc.new { |a, b| a + b }

# Procオブジェクトを指定
calc_result(100, 200, addition) #=> Using Proc: 300

# ブロックを指定
calc_result(100, 200) { |a, b| a - b } #=> Using Block: -100

# Proc・ブロックのいずれも指定しない
calc_result(100, 200) #=> No Proc or Block specified.

Proc #<< #>>

self と引数を合成した Proc を返します。

# Procオブジェクト1: 与えられた引数に1を加える
add_one = Proc.new { |x| x + 1 }

# Procオブジェクト2: 与えられた引数を2倍する
multiply_by_two = Proc.new { |x| x * 2 }

# 左合成 (Proc#<<): 最初にmultiply_by_twoを適用し、その結果をadd_oneに渡して実行する
composed_proc_left = add_one << multiply_by_two

# 右合成 (Proc#>>): 最初にadd_oneを適用し、その結果をmultiply_by_twoに渡して実行する
composed_proc_right = add_one >> multiply_by_two

# 引数用の値
num = 3

# 左合成の結果
result_left = composed_proc_left.call(num)       # (3 * 2) + 1
puts "Left Composition Result: #{result_left}"   #=> Left Composition Result: 7

# 右合成の結果
result_right = composed_proc_right.call(num)     # (3 + 1) * 2
puts "Right Composition Result: #{result_right}" #=> Right Composition Result: 8

lambda

Kernel.#lambda と Proc.new はどちらも Proc クラスのインスタンス(手続きオブジェクト)を生成しますが、生成された手続きオブジェクトはいくつかの場面で挙動が異なります。
lambda の生成する手続きオブジェクトのほうがよりメソッドに近い働きをするように設計されています。

l = lambda { |a, b| a - b }

p l.class #=> Proc
p l.lambda? #=> true

p l.call(10, 3) #=> 7
# Ruby1.9 ~ は、以下のようにも書くことが出来る
l = ->(a, b) { a - b }

p l.call(10, 3) #=> 7

proclambda の振る舞いの違い

引数の扱い

lambdaでは、引数の個数が違うと ArgumentError が発生する

pr = ->(a, b) { p a, b }
pr.call(10) #=> sample.rb:1:in `block in <main>': wrong number of arguments (given 1, expected 2) (ArgumentError)

Proc.new, procでは発生しない

pr = proc { |a, b| p a, b  }
pr.call(10)
#=> 10
#   nil

ジャンプ構文の挙動

def test_proc
  pr = Proc.new { return :from_proc }
  pr.call
  return :from_method
end

def test_lambda
  l = lambda { return :from_lambda }
  l.call
  return :from_method
end

def test_block
  tap { return :from_block }
  return :from_method
end

p test_proc   #=> :from_proc
p test_lambda #=> :from_method
p test_block  #=> :from_block

Proc.new は、ブロック内でのreturnと共に、呼び出し元のメソッドも抜けている。
lambdaは、ブロック内でreturnをするだけで、呼び出し元の後続処理は継続する。

return next break
Proc.new メソッドを抜ける 手続きオブジェクトを抜ける 例外が発生する
proc メソッドを抜ける 手続きオブジェクトを抜ける 例外が発生する
lambda 手続きオブジェクトを抜ける 手続きオブジェクトを抜ける 手続きオブジェクトを抜ける
イテレータ メソッドを抜ける 手続きオブジェクトを抜ける 手続きオブジェクトを抜ける

Procを生成したメソッドから脱出した後、手続きオブジェクトからの return, breakLocalJumpError が発生する。

def foo
  Proc.new { return :from_proc }
end

p foo.call #=> sample.rb:2:in `block in foo': unexpected return (LocalJumpError)

lambdaでは発生しない

def foo
  lambda { return :from_lambda }
end

p foo.call #=> :from_lambda
2
2
0

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
2
2