Feature #8497
closedprivate, protected, private_constなどがブロックを受け取るようにする
Description
RubyKaigi 2013のときにまつもとさんと話したことをチケットにします。
先に提案を書きます。
private
, protected
, private_const
など可視性を変更するメソッドがブロックを受け取るようにして、そのブロック内の可視性を変更するようにしてはどうか。
例:
private do
def some_private_method
end
end
def some_public_method
end
private_const do
PRIVATE_CONSTANT = true
end
メリットは以下のとおりです。
-
以下のように同じ名前を2回書く必要がない
PRIVATE_CONSTANT = true
private_const :PRIVATE_CONSTANT -
可視性変更の影響をブロック内だけに抑える実装ができる
(すでになかださんがパッチを持っているので、後でここに貼ってくれるはず。) -
構文に関してはまつもとさんから肯定的な意見をもらっている
「ブロックを使った構文はよさそうに感じる。ただ、試してみないと最終的にはわからないけど。」
デメリットは以下のとおりです。
-
前方互換ではない
古いRubyで以下のように書くとブロックが単に無視されメソッドが定義されない。
private do def some_private_method end end
背景です。
private
は以下のように現在のコンテキストの可視性を変更する
private
def xxx
end
という書き方もできるのにprivate_constant
は
private_constant :XXX
という書き方しかできないことにもやっとしていたので、その理由を聞いてみました。
理由は「コンテキストの可視性を変更するとちゃんとした実装にするのが大変そうだから。試していないけど、Threadを使ったりして並行にrequireしたときにおかしなことになるかもしれないし。普通はしないと思うけど、ちゃんと動くことを期待されそうじゃん。」という感じでした。
試しにやってみましたが、問題はありませんでした。
threaded-require.rb:
100.times do |i|
File.open("#{i}.rb", "w") do |rb|
rb.puts("class A")
rb.puts(" " + ["private", "protected", "public"][i % 3])
rb.puts(" Thread.pass")
rb.puts(" def x#{'%02d' % i}; end; Thread.pass")
rb.puts("end")
end
end
threads = []
50.times do |i|
threads << Thread.new(i) do |j|
require_relative("#{j}")
end
end
threads.each(&:join)
class A
p [:private, private_instance_methods(false).sort]
p [:public, public_instance_methods(false).sort]
p [:protected, protected_instance_methods(false).sort]
end
実行例(毎回同じ):
% /tmp/local/bin/ruby -v threaded-require.rb
ruby 2.1.0dev (2013-06-05 trunk 41090) [x86_64-linux]
[:private, [:x00, :x03, :x06, :x09, :x12, :x15, :x18, :x21, :x24, :x27, :x30, :x33, :x36, :x39, :x42, :x45, :x48]]
[:public, [:x02, :x05, :x08, :x11, :x14, :x17, :x20, :x23, :x26, :x29, :x32, :x35, :x38, :x41, :x44, :x47]]
[:protected, [:x01, :x04, :x07, :x10, :x13, :x16, :x19, :x22, :x25, :x28, :x31, :x34, :x37, :x40, :x43, :x46, :x49]]
もうひとつ以下のような強引な例を試してみたら、こっちは実行する毎に結果が変わりました。(こんな挙動になってもしょうがないんじゃないかという気もします。)
thread-def.rb
class A
threads = []
100.times do |i|
threads << Thread.new(i) do |j|
send([:private, :protected, :public][j % 3])
Thread.pass
end
end
Thread.pass
def a; end; Thread.pass
def b; end; Thread.pass
def c; end; Thread.pass
def d; end; Thread.pass
def e; end; Thread.pass
def f; end; Thread.pass
def g; end; Thread.pass
def h; end; Thread.pass
def i; end; Thread.pass
def j; end; Thread.pass
def k; end; Thread.pass
def l; end; Thread.pass
def m; end; Thread.pass
def n; end; Thread.pass
def o; end; Thread.pass
def p; end; Thread.pass
def q; end; Thread.pass
def r; end; Thread.pass
def s; end; Thread.pass
def t; end; Thread.pass
def u; end; Thread.pass
def v; end; Thread.pass
def w; end; Thread.pass
def x; end; Thread.pass
def y; end; Thread.pass
def z; end; Thread.pass
threads.each(&:join)
p [:private, private_instance_methods(false)]
p [:public, public_instance_methods(false)]
p [:protected, protected_instance_methods(false)]
end
実行例1:
% /tmp/local/bin/ruby -v thread-def.rb
ruby 2.1.0dev (2013-06-05 trunk 41090) [x86_64-linux]
[:private, [:b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n, :o, :p, :q, :r, :s, :t, :u, :v, :w, :x, :y, :z]]
[:public, [:a]]
[:protected, []]
実行例2(実行例1と結果が違っている):
% /tmp/local/bin/ruby -v thread-def.rb
ruby 2.1.0dev (2013-06-05 trunk 41090) [x86_64-linux]
[:private, []]
[:public, [:a, :b]]
[:protected, [:c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n, :o, :p, :q, :r, :s, :t, :u, :v, :w, :x, :y, :z]]
ブロックを使った書き方をサポートすると、並行に動いた時にもちゃんとした挙動になる実装にできそうな気がするのでブロックを使った書き方をサポートするのはどうでしょうか?
(「ちゃんと」とは何かをちゃんと決めないとだめそうな気がしますが。。。)
ブロックを使えるようになって、並行に動いた時にもちゃんとした挙動の実装だったら、以下のコードが毎回同じ結果になるはずです。
File.open("thread-block-def.rb", "w") do |rb|
rb.puts("class A")
rb.puts(" threads = []")
100.times do |i|
rb.puts(" threads = Thread.new do")
visibility = ["private", "protected", "public"][i % 3]
rb.puts(" #{visibility} do")
("a".."z").each do |character|
rb.puts(" Thread.pass")
rb.puts(" def #{character}#{'%02d' % i}")
rb.puts(" end")
rb.puts(" Thread.pass")
end
rb.puts(" end")
rb.puts(" end")
end
rb.puts("threads.join(&:each)")
rb.puts("end")
end
require_relative("thread-block-def")
class A
p [:private, private_instance_methods(false).sort]
p [:public, public_instance_methods(false).sort]
p [:protected, protected_instance_methods(false).sort]
end