クロージャ・to_proc
クロージャ
関数が内包するスコープを保持する性質
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株式会社