はじめに
Procは他言語を学んできた者でもRubyではじめてでてきた概念であるため、理解するのに苦労する。
「Procとは何か?」については大体理解できたが、それを使用すべき場面や使用することによるメリットというのを実感するために、Procで実装してあるものをProc無しでの実装を試みてみる。
Procとは
Rubyで頻繁に使用するブロックは色んな場面で持ち出して利用していきたいが、ブロックはオブジェクトではない。
オブジェクト指向であるRubyはオブジェクトとして捉えられると何かと便利であるので、ブロックもオブジェクト化したい。
そこで登場するのがProcであり、Procは「ブロックをオブジェクト化」したものである。
このProcの機能を活かした、Procを用いた実装例から深く学んでいく。
#Procを使用した実装例
文字列をエフェクトをする仕組みをProcを使用して実装する。
今回実装する文字のエフェクトは3種類。
・リバース...逆順 例)'apple',reverse=>'elppa'
・エコー...残響化 例)'apple',echo(2)=>'aappppllee'
・ラウド...大声 例)'apple',loud(3)=>'APPLE!!!'
そして、これらのエフェクトを混合させる。
(以下、このことをWordSynthと表記する)
echo(2)→loud(3)→reverse => '!!!EELLPPPPAA'
この仕組をまずはProcを使用して作成する。
#Procを使用して実装
まずはEffectsに関しての実装。
メソッドの中でProcを作成している。
module Effects
# reverseエフェクトについて関してのメソッド
def self.reverse
proc do |words|
words.split(' ').map(&:reverse).join(' ')
end
end
# echoエフェクトについて関してのメソッド
def self.echo(rate)
proc do |words|
words.chars.map{|c| c==' ' ? c:c*rate}.join
end
end
# loudエフェクトについて関してのメソッド
def self.loud(level)
proc do |words|
words.split(' ').map {|word| word.upcase + '!'*level }.join(' ')
end
end
end
WordSynthに関しての実装。
Effectsで実装したProcをcall()で呼び出す。
class WordSynth
def initialize
@effects =[]
end
#effectを配列に加える
def add_effect(effect)
@effects << effect
end
#配列に格納されたeffectを順々に実施
def play(original_words)
@effects.inject(original_words) do |words,effect|
effect.call(words)
end
end
end
Effects,WordSynthに関して実行させる
require './lib/effects'
require './lib/word_synth'
# Effectsに関して
effect1 = Effects.reverse
effect2 = Effects.echo(2)
effect3 = Effects.loud(3)
puts effect1.call('Ruby is fun!')
puts effect2.call('Ruby is fun!')
puts effect3.call('Ruby is fun!')
# WordSynthに関して
synth = WordSynth.new
synth.add_effect(Effects.echo(2))
synth.add_effect(Effects.loud(3))
synth.add_effect(Effects.reverse)
puts synth.play('Ruby is fun!')
実行結果
ybuR si !nuf
RRuubbyy iiss ffuunn!!
RUBY!!! IS!!! FUN!!!!
!!!YYBBUURR !!!SSII !!!!!NNUUFF
Procを使用することによって、WordSynthを作成することはできた。
なんとなくProcを使用すると都合良く作成できた感触はある。
しかし、イマイチその威力がわかっていない。
Procを使わずに突き進もうとしたらどうなっていたのか検証してみた。
#Procを使用せずに実装を試みる
まずはEffecsの実装。
Procの引数で与えていた(words)をどうするか、、
module Effects
def self.reverse(words)
words.split(' ').map(&:reverse).join(' ')
end
def self.echo(words,rate)
words.chars.map{|c| c==' ' ? c:c*rate}.join
end
def self.loud(words,level)
words.split(' ').map {|word| word.upcase + '!'*level }.join(' ')
end
end
とりあえず、メソッドに第一引数,第二引数という形で引数wordsを与えた。
Procを使用せずとも問題ないのでは??
続いて、WordSynthの実装。
class WordSynth
def initialize
@effects =[]
end
def add_effect(effect)
@effects << effect
end
# Procを使用していないとplay(original_words)を使用できない
# def play(original_words)
# @effects.inject(original_words) do |words,effect|
# effect.call(words)
# end
# end
end
Effectsの時点で引数に(words)を与えてしまうため、先程のplay(original_words)をうまく作ることができない。
そもそもこれでは@ effects =[]に格納するものがeffect機能ではなくて、既に与えられた文字を変換した文字列にしかならない。
何か工夫すればProcを使用せずとも同じ機能をもちあわせるメソッドを作成できそうではあるが、Procを使用すると簡単に上記の機能を作成することができるというのはわかった。
#Procを使用するメリットについて
検証からProcの強みを2つ実感した。
・メソッドが受け取れるブロックの数は最大で1つであるが、Procを利用すれば複数のブロックを使用できるということ。
・callのタイミングで引数を与えることができるということ。
play(original_words)ではcallを使用することで、メソッドの中で2つのブロックが機能していていることがわかる。
また、引数(words)をcallのタイミングで入れることができるという点も特徴的である。
反対にProcを使用していない方は、1つのメソッドにブロックは1つであり、引数(words)を与えるタイミングも他の引数と同時でなくてはいけなくなる。
#まとめ
Procを使用するメリットは他にもたくさんあると思うが、あくまで今回の検証からは2つのメリットを実感できる。
プログラミングの学習の際、どういった機能であるかを理解することも大事であるが、それをどの場面に使用すべきかを自分なりにイメージできるようになることも大切である。
#参考文献
この記事は以下の情報を参考にして執筆しました。
- プロを目指す人のためのRuby入門 伊藤 淳一[著]