Ruby
Proc

Procについて勉強したことを書く

Procとは何か?

Procとはブロックをオブジェクト化し、柔軟に呼び出すことができるクラスです。
自身のイメージとしては、わざわざメソッドを作らなくても良い処理を書きたい時や、メソッド自体に機能の追加はせずに、Proc オブジェクトなどをメソッドに渡し、処理を変えたい時に使えます。今回はやさしいRubyを教材に、勉強した内容についてまとめました。(間違っているならば、指摘してくださると助かります。)

Procの生成/実行方法

ブロックをオブジェクト化する時は、"object = Proc.new do |ブロック変数| 処理内容 end "と記述します。
サンプルにブロックの中にxの2乗を計算します

proc.rb
sample = Proc.new do |x|
   x**2 
end

また、ブロックの処理を実行するには "Procで生成したオブジェクト.call(引数)"の形式で使う必要があります。引数にはブロック変数に渡す値を入力します

proc.rb
sample = Proc.new do |x|
    x**2
end

#実行方法
puts sample.call(2) # => 4
puts sample.call(5) # => 25
puts sample[10]     # => 100 "オブジェクト[引数]"の形でも呼び出せる

ちなみに配列をブロック変数に渡すこともできます。
サンプルに全ての要素に ×2を行い、配列を返り値とした例です。

proc.rb
sample2 = proc do |*args|
    args.map { |i|
        i * 2
    }
end
sample2.call(1,2,3) # => [2,4,6]

lambda

Procオブジェクトの生成方法、呼び出し方法が分かったので、次にlambdaについて解説を行います。lambdaとはProcオブジェクトを生成するメソッドのことで、前述に述べたProcとは異なる点があリます

1つ目が引数の数のチェックが厳しいこと
例えば、ブロック変数と、メソッドの引数が違うと、エラーが発生します
以下サンプルです

proc.rb
lambda1 = lambda do |i,j,k|
    pp [i,j,k]
end

lambda1.call(1,2,3) # => [1,2,3]

#proc1.call(1,2) # 引数が違うので、(given 2, expected 3) (ArgumentError) が表示される

2つ目がreturn文を記述して、prcoオブジェクトを返すことができます。

proc.rb
def calc(x)
  lambda do |y|
      puts "a"
      return x - y
  end
end

sample2 = calc(10) #メソッド側の引数xに10を代入
puts sample2.class # => Proc 、クラスは当然proc
puts sample2.call(3) # => 7 、return 10 - 3なので、7となる

上記のサンプルを見ると、calcメソッドを実行するとreturnで帰ってくるのは数値オブジェクトではなく、Procオブジェクトでした。
そしてProcオブジェクトから.callメソッドを呼び出すことによって、数値オブジェクトが返されていることが分かります。

Procの特徴

Procはクラスを作らなくても、オブジェクトの状態を保持することが出来るという特徴があります。
例えばわざわざクラスを作らなくても良いが、オブジェクトの状態は保持しておきたい時に使用することが多いです。(マスターしたら、色々なコードが書けそう)

サンプルとして足し算の計算結果を保持するプログラムを書きます。

proc.rb
def calc
  #計算結果の格納
  result = Array.new

  proc do |x,y|
    z = x + y
    result.push(z)
    puts "#{z}"
    puts "result :#{result}"
  end
end

sample = calc

sample.call(1,2) # => 3
sample.call(2,4) # =>  6
sample.call(100,24) # => 124

上記を実行すると、計算結果を配列に格納し、保持することが出来ます。
また、最後の実行結果として、"result : [3,6,124]"が出力されています。

Prcoを使いたい時のサンプル

実際にProcを使って、私が作成したサンプルコードを元に解説したいと思います。
ソースコードについて解説すると、Animalというスーパークラスを作成し、Catクラス、LionクラスがAnimalクラスを継承しています。クラスの振る舞いとしては、動物が走った時にカロリーを消費している。
また、ネコやライオンの個体によってはカロリー消費が異なると仮定して、runメソッドの引数にブロックを渡せるようにCatクラスとLionクラスで実装しました。

Animal.rb
class Animal
  #カロリーの初期値を入力
  def initialize(cal)
      @calorie = cal
  end
  #カロリーを消費
  def run
  end

  #カロリーを表示
  def get_calorie
      return @calorie
  end
end

class Cat < Animal

  def run(&block)
    if block   #ブロックが存在しているか?
      @calorie = block.call(@calorie) #ブロックの変数にcalorie
    else
      @calorie = @calorie - 100       #通常の消費カロリー
    end
  end
end

class Lion < Animal

  def run(&block)
    if block   #ブロックが存在しているか?
      @calorie = block.call(@calorie) #ブロックの変数にcalorie
    else
      @calorie = @calorie - 400       #通常の消費カロリー
    end
  end
end

上記を実行するためのプログラムを以下に記述しました。
また、実行結果は3パターン用意しました。

Animal.rb
#(1)通常
cat1 = Cat.new(1000)
cat1.run()
puts cat1.get_calorie # => 900

#(2)消費するカロリーをブロックでrunメソッドに渡す
cat2 = Cat.new(1000)
cat2.run{ |calorie| calorie - 200 } #猫の個体によって処理を変えたい
puts cat2.get_calorie # => 800

#(3)procの生成から、ブロックを作成し、仮引数に渡す。
cat_cal = Proc.new do |calorie|
    calorie - 300
end
cat3 = Cat.new(2000)
cat3.run(&cat_cal)
puts cat3.get_calorie #=> 700

呼び出し元のメソッドを意識することなく、Procを使用することによって、処理を柔軟に行うことができました。

最後に

今回は備忘録にまとめて見ました。
修正 + 追記などを随時行います。

参考

やさしいRuby