こんにちは。いとじゅんと申しまする。
やっとメタプログラミングRubyを再開したので、まとめてみました。
ブロックさん
ブロックさんは波括弧または do...endキーワードで定義できる。ブロックはメソッド呼び出し時にだけ定義できる。
# これはできない
do
"hoge"
end
# これはいける
hoge_method do
"hoge"
endメソッドに渡されたブロックは、メソッド内部でyieldキーワードによって、任意のタイミングで実行できる。yieldの意味がやっと理解できた気がする。
def sunny
puts "今から晴れるよ"
yield("快晴")
end
sunny do |word|
puts "今日の天気は...#{word}!"
end
# 今から晴れるよ
# 今日の天気は...快晴!ブロックを定義した時、参照した変数などの束縛を持っていくため、クロージャと言える。クロージャってなんですか。
自分で作られたスコープの環境を記憶して持ち運べる処理の塊 by Gemini
ほへ〜。意識せずに使っていた。
スコープ
Rubyのスコープは、下位のスコープから上位のスコープの変数などに参照することができない。トップレベルなどに定義した変数は、下位に定義されたクラスやメソッドなどで参照できない。
look_me = "loooooook me!!!"
def fuga
p look_me
end
fuga # raise NameErrorスコープゲート
def, class, moduleキーワードはスコープを切り替えて、新しいスコープを生成する。これらのキーワードは先ほど書いたスコープの性質によって、上位スコープへのアクセスが不可となる。
スコープゲートから逃れるためには、スコープゲートのキーワードを使わずに、メソッドやクラス、モジュールを定義すれば良い。
look_me = "loooooook me!!!"
define_method :fuga do
p look_me
end
fuga # => "loooooook me!!!"クラスならClass.new, モジュールならModule.newで行ける。このような、スコープを均して上位スコープに参照する方法をフラットスコープと呼ぶ。
#instance_eval
BasicObject#instance_evalに渡したブロック内では、レシーバをselfにして評価される。よって、そのオブジェクトの束縛にアクセスできるようになる。これ何に使うんだろうと思ったら、デバッグとかテストで使うことがあるそう。なるほどね。ここで渡されたブロックのことをコンテキスト探査機と呼ぶ。強そう。
class Hoge
def initialize
@fuga = "piyo"
end
end
Hoge.new.instance_eval do
self # => #<Hoge:0x130e9d98 @fuga="piyo">
@fuga # => "piyo"
endまた、オブジェクトの束縛へアクセスできるため、カプセル化が破壊される。ひえ〜。
class Hoge
private
def private_method
"いやーん、プライベートよー"
end
end
Hoge.new.instance_eval do
private_method # => "いやーん、プライベートよー"
endクリーンルーム
よくわからなかった。上位スコープに定義された束縛から隔離するために使う?また今度聞く。
呼び出し可能オブジェクト
Rubyにはブロック以外にコードを保管して後から呼び出せる場所が少なくとも以下の3つがある。
- Procオブジェクト
- lambda(Procのオブジェクトではあるが、微妙に違う)
- メソッド
ちなみに、ただのブロックはオブジェクトではない。
Procオブジェクト
先ほど言った通り、ただのブロックはオブジェクトではないが、これだとコードを保管して後から呼び出す時に困る。そこで現れた救世主こそ、Procだ!!!!!これはブロックをオブジェクトにしたもの。
以下のように定義して実行できる。Proc.newで定義、Proc#callで呼び出すことができる。
yaoyoro = Proc.new {|user|
"#{user}のみんなー!ヤオヨロー!"
}
yaoyoro.call("神々") # => "神々のみんなー!ヤオヨロー!"
yaoyoro.class # => ProclambdaはProcのオブジェクトだが、いろいろ違う。まずは以下のように定義できる。
kaguya = lambda { "かぐやっほー!" }
kaguya.call() # => "かぐやっほー!"
kaguya.class # => Proclambdaと他のProcの大きな違いは2つある。
1つ目にreturnの挙動が異なる。lambda内のreturnは単にlambdaから返るだけだが、Proc内のreturnはProcが定義されたスコープから返る。みた方が早いと思う。
# 通常のProcの場合
def kaguya_hello
kaguya = Proc.new { return "かぐやっほー!" } # ここのreturnでもう返っちゃう
result = kaguya.call
return result * 2 # ここまで来ない!
end
# lambdaの場合
def yachiyo_hello
yachiyo = lambda { return "ヤオヨロー!" }
result = yachiyo.call
return result * 2 # ここで返される
end
kaguya_hello # => "かぐやっほー!"
yachiyo_hello # => "ヤオヨロー!ヤオヨロー!"2つ目の違いは引数のチェック方法。Procは引数の数が多くても少なくても、 ArgmentErrorを返さない。柔軟すぎるかもね。それに対して、 lambdaは引数の数に厳格。
p = Proc.new { |a, b| [a, b] }
p.call(1) # => [1, nil]
p = lambda { |a, b| [a, b] }
p.call(1) # => raise ArgumentErrorlambdaの方が通常のメソッドに近いと覚えておいたら良い。
&修飾
yieldを使うことでメソッド内でブロックに与えた処理を実行できるが、 yieldじゃ足りないケースがある。例えば、他のメソッドにブロックを渡したいときやブロックをProcに変換したいときなど。
&修飾を使うことでメソッドに与えたブロックを束縛し、引数として受け取ることができる。逆にProcに対して &修飾を付加してメソッドに与えると、ブロックに戻すことができる。
def hello(word, num)
yield(word, num)
end
def do_hello(word, num, &block) # 引数としてブロックをProcに変換して受け取っている
block.class # => Proc
hello(word, num, &block) # Procに対して&を使ってブロックに戻して#helloを呼び出している
end
do_hello("かぐやっほー!", 3) {|word, num| word * num } # => かぐやっほー!かぐやっほー!かぐやっほー!Methodオブジェクト
以下のコードのようにオブジェクトに対して、 #methodを使うことでMethodオブジェクトとして取得できる。これは Method#callを使うことで実行できる。
class CPK
def initialize(value)
@x = value
end
def hello
@x
end
end
object = CPK.new("かぐやっほー")
method = object.method :hello # => #<Method: CPK#hello() eval:6>
method.call # => "かぐやっほー"Procなどとの違う点として評価されるスコープが異なる点がある。Procは定義された時のスコープで評価されるが、Methodは所属するオブジェクトのスコープで評価される。
UnboundMethod
クラスやモジュールから引き離したメソッドは UnboundMethodクラスのオブジェクトになる。呼び出し不可だが、 #bindを使うことでオブジェクトへ束縛でき、呼び出せるようになる。ただし、メソッドの元々の束縛先がクラスの場合は元のクラスまたはそのサブクラスにしか束縛できない。モジュールの場合は特に制限はない。
class CPK
def initialize(value)
@x = value
end
def hello
@x
end
end
object = CPK.instance_method(:hello)
object.class # => UnboundMethodActiveSupportなどで使われているらしいが、正直どこで使うべきか理解していないのでこれは宿題です。
おわりに
ここまでやった私は以下のリポジトリの演習問題をやることで、理解したような気がしています。私と同じようにメタプログラミングを学んでいる同志におすすめです。
この章を通して、Rubyのスコープやブロックの取り扱い方を学べました。スコープゲート周りとかはメタプログラミング関係なく覚えておくと業務に役立ちそうですね。
ではまたー!
