ブロック
C#にはusing
と呼ばれる構文が存在し、引数で受けたオブジェクトに対して処理を行ったあと、またはその処理の中で例外が発生したあと、必ずdispose
というメソッドを呼び出し、引数で受けたオブジェクトの廃棄を行う。
これをRuby風に書くと以下のようになる。
module Kernel def with(resouce) begin yield if block_given? ensure resouce.dispose end end end class Running def raise_error raise "Opps!!" end def pause(sec) puts "Now I am having rest." sleep(sec) end def dispose puts "Bye!!" end end runner = Running.new with(runner){ runner.pause(3) # runner.raise_error }
withメソッドに対して、Runnerオブジェクトと、それに対して行いたい操作をブロックで渡している。
コメントアウトしたメソッドはRunnerオブジェクトに対して例外を発生させるメソッドであるが、これをコメントアウトしてもしなくてもdispose
メソッドが実行される。
irb(main):004:0> require "./using.rb" Now I am having rest. Bye!! Traceback (most recent call last): 8: from /home/pyons/.rbenv/versions/2.5.0/bin/irb:11:in `<main>' 7: from (irb):4 6: from /home/pyons/.rbenv/versions/2.5.0/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require' 5: from /home/pyons/.rbenv/versions/2.5.0/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require' 4: from /mnt/c/Users/broad/OneDrive/products/METAprogramming/Wednesday/using.rb:28:in `<top (required)>' 3: from /mnt/c/Users/broad/OneDrive/products/METAprogramming/Wednesday/using.rb:4:in `with' 2: from /mnt/c/Users/broad/OneDrive/products/METAprogramming/Wednesday/using.rb:30:in `block in <top (required)>' 1: from /mnt/c/Users/broad/OneDrive/products/METAprogramming/Wednesday/using.rb:13:in `raise_error' RuntimeError (Opps!!)
例外発生をコメントアウトしても全く同様の動きになる。
irb(main):005:0> require "./using.rb" Now I am having rest. Bye!! => true
ここまではブロックの使い方のおさらい。
ブロックに関しては既にSilverの勉強で扱っていて、その時「拘束」という概念も一応理解はしているつもりだった。
メソッドを呼び出すときに、ブロック内で初期化された変数はメソッド内での処理が終了すると消滅し、参照することは出来ない。自身が定義された(呼び出されたメソッドの内部という)環境でしか解決できないような「処理生成時の環境を拘束する」ことから「束縛」と呼ばれ、これを可能にするものを「クロージャ―」と呼ぶ。
Ruby Silver 試験対策 Day3 (ブロックとProcと例外編) - Pyons Tech Blog
けどこれは、あまり正しい理解ではない気がする。拘束しているのは「ブロックの内部」というよりも、ブロックの前までで定義されていたローカル変数を束縛して、処理先に連れて行っている、と考えるほうが自然なようだ。
個人的には「ローカル変数のスナップショットを処理へ連れていく」というイメージを持っているが、検索した限り同じような説明をしている人はいなかった。
def, module, classといった変数のスコープが変化するようなコード内での区切りを「スコープゲート」と呼ぶ。スコープゲートを超えて変数を渡すにはどうすればよいか。
スコープゲートを用いづにクラス定義、メソッド定義を行えばよい、というのが答えになる。
my_var = "スコープを超えたよ。" MyClass = Class.new do puts "Inside Class definition: #{my_var}" define_method :my_method do puts "Inside Method definition: #{my_var}" end end m = MyClass.new p '&&&&&&&&&&' m.my_method
特殊なクラス定義、メソッド定義を用いて、各定義をブロックの内部で行った。これにより、ブロック以前の変数を束縛して内部に連れていくことが可能になった。
pyons@LAPTOP-SF87NLCB:/mnt/c/Users/broad/OneDrive/products/METAprogramming/Wednesday$ ruby flat_scope.rb Inside Class definition: スコープを超えたよ。 "&&&&&&&&&&" Inside Method definition: スコープを超えたよ。
この手法を「フラットスコープ」と呼ぶ。
フラットスコープを用いるとこのようなことが可能になる。
def hello shared = 0 define_method :counter do shared end # def counter # shared # end define_method :incriment do |x| shared += x end # def incriment(x) # shared += x # end end hello p counter incriment(4) p counter
フラットスコープを使って定義したメソッド同士では変数が共有されている。define_method
は単に``def increments(x)```に書き直すだけではなく、定義したメソッドそのものも特殊なスコープを持つようになるのだ。
pyons@LAPTOP-SF87NLCB:/mnt/c/Users/broad/OneDrive/products/METAprogramming/Wednesday$ ruby shared_scope_copy.rb 0 4
この他にブロックを用いた例としてinstance_eval
がある。instance_eval
はブロックを使って、クラスやメソッドの中のローカル変数あプライベート変数にアクセスすることが出来る。ブロックの中で、指定したクラスやメソッドのレシーバーをself
にできるため、あたかもクラスやメソッドの内部のコンテキストにいるように感じられる。
class MyClass variable = "This cannnot be accessed from out side." def initialize @v = 1 end p variable p self # p self.variable # undefined error end obj = MyClass.new obj.instance_eval do p self # <MyClass:0x007ffff6a16de8 @v=1> p @v # 1 end MyClass.instance_eval do p self # MyClass p @v #nil # p variable # undefined error (クラス内でselfのレシーバーを付けても同様) end obj2 = MyClass.new obj2.instance_eval do @v = "Hello" p @v end
ここで気づいたのは「クラス内で宣言したローカル変数はprivateメソッドのように振る舞う」ということである。instance_evalを用いずにクラス内でレシーバーを付けて同じクラス内のローカル変数にアクセスしようとしてもundefinedになってしまう。
Rubyのprivateメソッドはかなり特殊で以下のようなこともできない。
class MyClass variable = "This cannnot be accessed from out side." def initialize @v = 1 end p variable p self p hello private def hello p "hello" end end
プライベートメソッドであるhello
にクラス内からアクセスすることが出来ない。このプライベートメソッドにアクセスしたければ、以下のようにクラス内のメソッドから呼び出す必要がある。
def call_hello hello end
クラス内でprivateメソッドを呼び出しているとき、そのselfは「クラス」そのものである。
一方メソッドから呼び出しているときは、selfは「オブジェクト」になっている。メソッドはクラス内で直接呼び出すことは出来ない。selfがクラスになっている限り、メソッドは呼び出せない。
def call_hello hello end p call_hello
insttance_eval.rb:8:in `<class:MyClass>': undefined local variable or method `call_hello' for MyClass:Class (NameError) Did you mean? caller
Silverの勉強で扱ったように、privateメソッドはレシーバー付きで呼び出すことが出来ない。呼び出すには、オブジェクト>publicメソッド>privateメソッド
のように扱う必要がある。
ところが、クラス内のローカル変数は更に奇妙なアクセスバリデーションを設けているようだ。研究すると長くなりそうなので、この件について後日追記したい。