[ruby-trunk - Feature #8497][Open] private, protected, private constなどがブロックを受け取るようにする

Issue #8497 has been reported by kou (Kouhei S.).


Feature #8497: private, protected, private_constなどがブロックを受け取るようにする

Author: kou (Kouhei S.)
Status: Open
Priority: Normal
Assignee: matz (Yukihiro M.)
Category: core
Target version:

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

Issue #8497 has been updated by shyouhei (Shyouhei U.).

headius が反対しているという事実を指摘しておきます。 [ruby-core:47572]
卜部は英語として読み下した時の違和感が気になります。 [ruby-core:47556]

Feature #8497: private, protected, private_constなどがブロックを受け取るようにする

Author: kou (Kouhei S.)
Status: Open
Priority: Normal
Assignee: matz (Yukihiro M.)
Category: core
Target version:

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