Project

General

Profile

Feature #8497

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

Added by kou (Kouhei Sutou) about 7 years ago. Updated over 2 years ago.

Status:
Rejected
Priority:
Normal
Target version:
-
[ruby-dev:47401]

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

Related issues

Is duplicate of Ruby master - Feature #7019: allow `private` and `protected` keywords to take blocksRejectedmatz (Yukihiro Matsumoto)Actions

Updated by shyouhei (Shyouhei Urabe) about 7 years ago

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

#2

Updated by nobu (Nobuyoshi Nakada) over 5 years ago

  • Description updated (diff)

Updated by kou (Kouhei Sutou) over 2 years ago

  • Status changed from Open to Rejected

#3753private def ...と書けるようになって必要なくなったので閉じます。

Also available in: Atom PDF