パーフェクトRubyお勉強フェスティバル 第8章 「Procオブジェクト」

oftonkingdom.hatenablog.com

8章,Procオブジェクトです.

関数型プログラミングっぽい話が多くて面白かった!

Procクラス

ブロックを扱いやすくするのがProcオブジェクト,というイメージをなんとなく持ちました.

Procオブジェクトのいいところは,ブロックをオブジェクトの形にして扱うことができるし,メソッドに対して複数ブロックを渡すということも可能になるというところですかね.

Proc#callに引数を渡す場合は別の書き方もできる.(忘れそう)

proc_obj = Proc.new do |name|
  puts "こんにちは#{name}さん"
end

#以下は全部同じこと
proc_obj.call("太郎")
proc_obj["太郎"]
proc_obj.("太郎")

覚えておきたい,メソッド呼び出し時のブロックとProcオブジェクト

「&」を頭につけることで,以下が可能となる.

  • メソッド呼び出し時にブロックを指定した場合に,メソッド側ではProcオブジェクトとして扱う
  • メソッド呼び出し時にProcオブジェクトを指定した場合に,メソッド側ではブロックとして扱う
#引数名に「&」をつけた仮引数により,渡されたブロックをProcオブジェクトとして受け取れる
def ofton(&block)
  block #Procオブジェクトを返却
end

proc_obj = ofton{puts "Hello"} #ブロックを渡す
proc_obj.call #=> "Hello"


#yieldで呼び出したいブロックを,メソッド呼び出し時にProcオブジェクトとして渡す
def ofton2
  yield
end

proc_obj2 = Proc.new {puts "Hello"}
ofton2 &proc_obj2 #=> "Hello"

カリー化

関数型プログラミングについて勉強してくると出て来るカリー化ですね.

複数の引数を持つメソッドに対し,例えば第一引数だけ渡してメソッド呼び出しを行うと,残りの引数をとるメソッドを返してくれるというやつです.

ofton = Proc.new {|name, status| "#{name}#{status}"}
p curried_ofton = ofton.curry.("おふとん国王") #「name」にのみ値を渡しカリー化する

p curried_ofton.("眠い") #「status」に値を渡す

結果

#<Proc:0x00000000e8a378>
"おふとん国王は眠い"

一つ疑問に思ったのが,上の例だとname,statusという順番で引数が定義されているが,statusにのみ値を渡しカリー化することはできるのか?

ちょこっと調べた限りだとできなそう(何か方法はあるのかもしれないが).というかそれはもう「カリー化」とは言わないのか.

ところでカリー化よく出てくるけど,何がありがたいんだろう…という疑問.

teratail.com

qiita.com

ラムダ

Kernel.#lambdaでProcオブジェクトを作ることも可能.なお,これにはシンタックスシュガー「->」がある.なぜ「->」かというとこれが「λ」に似ているから…らしい(似ているかどうかは知らない).

とは言え他言語でも出て来るラムダ式の由来を知ることができてよかった.

#Kernel.#lambdaを使う
lambda_obj = lambda { |name| puts "こんにちは#{name}さん"}

#簡潔に書けるシンタックスシュガー
lambda_obj2 = ->(name) {puts "こんにちは#{name}さん"}

lambda_obj.("太郎")
lambda_obj2.("太郎")

結果

こんにちは太郎さん
こんにちは太郎さん

Proc.newとKernel.#procとKernel.lambda

これらはいずれもProcオブジェクトを作り出せるが,これらによって作られたProcオブジェクト違いとして

  • returnとbreakの挙動の違い(return時,Kernel.lambdaは制御を抜ける,Proc.newとKernel.#procではメソッドを抜ける.break時,Kernel.lambdaは制御を抜ける,Proc.newとKernel.#procでは例外が発生する.)
  • 引数の違い(Kernel.lambdaは引数の数が一致しないとArgumentErrorを発生させる.Proc.newとKernel.#procでは,多く渡されたらその分を無視,少なかったらnilを渡す,などの対応をする.)

がある.基本的にKernel.lambdaはメソッドと同じ挙動となる.

クロージャ

これも関数型プログラミングの勉強をしているとよく出てくる.が,未だにピンと来ていなかったりした.

そこでまず,クロージャの利点みたいなものを調べてみる.

d.hatena.ne.jp

JavaScriptの記事ですが,前よりはなんとなく使い所がわかった気がする.

そして,Rubyにおけるクロージャへ.クロージャのようにProcオブジェクトを使う例.Proc.newしたコンテキストでの「status」を保持していることがわかる.

def ofton_proc
  status = 'ofton'
  Proc.new { status }
end

proc_obj = ofton_proc

status = 'toplevel'
p proc_obj.call

結果

"ofton"

いろいろ調べてみるとRubyクロージャをやろうとするときにProcオブジェクトを使う理由がわかってくる.

JavaScriptだったら関数の中に関数を定義することで実現しているけど,Rubyでメソッドの中にメソッドを定義したとしても内側のメソッドからは外側のメソッドのスコープの変数は参照できないわけですもんね.