Project

General

Profile

Feature #8497

Updated by nobu (Nobuyoshi Nakada) over 9 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 
 ~~~ 

Back