クロージャ・to_proc

クロージャ

関数が内包するスコープを保持する性質

rubyのような手続型プログラミングにも、関数型プログラミングのような記法が存在する。

 

いわゆるブロックがrubyでいうクロージャに当たる。

rubyでは、do endや{}で囲まれた、メソッドのような手続きをブロックで表現する書き方が存在している。

 

ブロックはそれ単独ではメモリ上に存在することができない。

 

そのため、ブロックをメモリ上に残しておきたい場合は、Procという手続きオブジェクトを使用する必要がある。

 

ブロックは、外側で定義されたローカル変数を参照できるという性質を持っていて、Proc.newの引数に渡すことでメモリ上に残ることができる。

n = 0

count = Proc.new { n+= 1 }
count.call #=> 1
count.call #=> 2
count.call #=> 3

また、以下の例から分かる通り、callをprocオブジェクトレシーバにつけることによって、ブロック内の処理を実行することができる。

Proc.newは下記のように書き換えることが可能

n = 0

count_tilda = -> { n+= 1 }
count_lambda = lambda { n+= 1 }
count_proc = proc { n+= 1 }

count_tilda.call #=> 1
count_lambda.call #=> 2
count_proc.call #=> 3

この、値をカウントする処理をclassにして表現することも可能。

class Counter
  attr_reader :count

  def initialize(count = 0)
    @count = count
  end

  def increment(n = 1)
    @count += 1
  end
end

counter = Counter.new
counter.increment
#=> 1

Symbol#to_proc

シンボルオブジェクトにはto_procメソッドが存在している。

シンブルオブジェクトに対してto_procを実行すると、Proc.new(レシーバの引数).レシーバが暗黙的に実行される。

例えばシンボルオブジェクトのメソッドであるequalを実行してみる。

:equal?.to_proc['1',1]  # => false ← '1'.equal?1と同じ
:eql?.to_proc[12,12]  # => true ← '1'.eql?1と同じ

上記のように明示してto_procを呼び出す方法もあれば、下記のように暗黙的に呼び出されるケースもある。

よく見る、&にシンボルオブジェクトを渡すケース。


# メソッドに & とともにシンボルを渡すと
# to_proc が呼ばれて Proc 化され、
# それがブロックとして渡される。
(1..3).collect(&:even?)  # => ["2"]
(1..3).select(&:odd?)   # => [1, 3]

Hash#to_proc

ハッシュオブジェクトにもto_procメソッドが存在している。

hash = { lemon_sour: 'kan', beer: 'bin', wine: 'bottle' }

# to_procを使わない場合
without_to_proc = proc { |key| hash[key] }
without_to_proc.call(:lemon_sour)
#=> "kan"
without_to_proc.call(:sake)
#=> nil

# to_procを使う場合
with_to_proc = hash.to_proc
with_to_proc.call(:lemon_sour)
#=> "kan"
with_to_proc.call(:sale)
#=> nil

&をpefixにつけると、ブロックを引数に取ることができる。

hash = { japan: 'yen', us: 'dollar', india: 'rupee' }

[:japan, :india, :italy].map { |country| hash[country] }
#=> ["yen", "rupee", nil]

[:japan, :india, :italy].map(&hash)
#=> ["yen", "rupee", nil]

Method#to_proc

mehodオブジェクトにもto_procが存在している

メソッドでProcオブジェクトに変換することができる。

実行時の引数はそのまま元のメソッドの引数(selfを取る)になり、元のメソッドが実行されまているのが分かる。

def greet_name(name)
  `hello #{name} !`
end

method_hello = self.method(:greet_name)
#=> #<Method: Object#greet_name>

proc_hello = method_hello.to_proc
proc_hello.call('Subaru')

#=> hello Subaru !

Symbol#to_proc (Ruby 3.1 リファレンスマニュアル)

Rubyで関数型プログラミング#2: クロージャ(翻訳)|TechRacho by BPS株式会社

Kernel.#block_given? (Ruby 3.1 リファレンスマニュアル)

Rubyのブロック(クロージャ)はローカル変数をインスタンス変数に変えるマジックだ!