Feature #8497
Updated by nobu (Nobuyoshi Nakada) almost 10 years ago
RubyKaigi 2013のときにまつもとさんと話したことをチケットにします。 先に提案を書きます。 `private`, `protected`, `private_const`など可視性を変更するメソッドがブロックを受け取るようにして、そのブロック内の可視性を変更するようにしてはどうか。 private, protected, private_constなど可視性を変更するメソッドがブロックを受け取るようにして、そのブロック内の可視性を変更するようにしてはどうか。 例: ~~~ruby private do def some_private_method end end end def some_public_method end private_const do PRIVATE_CONSTANT = true end ~~~ メリットは以下のとおりです。 * 以下のように同じ名前を2回書く必要がない PRIVATE_CONSTANT = true private_const :PRIVATE_CONSTANT * 可視性変更の影響をブロック内だけに抑える実装ができる (すでになかださんがパッチを持っているので、後でここに貼ってくれるはず。) * 構文に関してはまつもとさんから肯定的な意見をもらっている 「ブロックを使った構文はよさそうに感じる。ただ、試してみないと最終的にはわからないけど。」 デメリットは以下のとおりです。 * 前方互換ではない 古いRubyで以下のように書くとブロックが単に無視されメソッドが定義されない。 ~~~ruby private do def some_private_method end end end ~~~ 背景です。 `private`は以下のように現在のコンテキストの可視性を変更する ~~~ruby privateは以下のように現在のコンテキストの可視性を変更する private def xxx end ~~~ という書き方もできるのに`private_constant`は ~~~ruby という書き方もできるのにprivate_constantは private_constant :XXX ~~~ という書き方しかできないことにもやっとしていたので、その理由を聞いてみました。 理由は「コンテキストの可視性を変更するとちゃんとした実装にするのが大変そうだから。試していないけど、Threadを使ったりして並行にrequireしたときにおかしなことになるかもしれないし。普通はしないと思うけど、ちゃんと動くことを期待されそうじゃん。」という感じでした。 試しにやってみましたが、問題はありませんでした。 threaded-require.rb: ~~~ruby 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") rb.puts("end") end end end threads = [] 50.times do |i| threads << Thread.new(i) do |j| require_relative("#{j}") require_relative("#{j}") end 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 ~~~ruby class A threads = [] 100.times do |i| threads << Thread.new(i) do |j| send([:private, :protected, :public][j % 3]) Thread.pass Thread.pass end 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") 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 ~~~