2010年9月13日月曜日

Rubyのprivateメソッドルールの例外

privateメソッドのルール

『メタプログラミングRuby』P.65のコラムに、
privateメソッドのルールについて書かれていました。

 ”プライベートメソッドには1つのシンプルなルールが適用される。それは、「明示的なレシーバをつけてプライベートメソッドを呼び出すことはできない」というものだ。つまり、プライベートメソッドは、暗黙的なレシーバselfに対するものでなければならない。”

「privateメソッドは、明示的なレシーバをつけられない」。
レシーバがない状態といえば、「(暗黙的に)selfをレシーバとする状態」しかないわけですから、
通常はそのprivateメソッドを持っているオブジェクトからじゃないとメソッドを参照する経路がありません。
これによってRubyのprivateメソッドが「プライベートな」存在になるのですね。
(本気を出せば破ってしまえますが、それはまた別の話。)

これはわかりやすい覚え方です。
具体的に、極端な例を試してみると以下のようになります。

class A
  def foo
    self.bar
  end

  private

  def bar
    puts "bar is private method"
  end
end

A.new.foo # => error

例え同じクラス内からでも、明示的にレシーバ(ここではself)を
書いてしまうとエラーが起きます。

ルールの例外

しかし、このルールには例外が一つあります。
それは「セッターメソッドには明示的なレシーバが必要」、ということです。
(セッターメソッドというのは、メソッド名が「=」で終わるもののこと)

先程の例を、privateメソッドをセッターメソッドに変えてやってみましょう。

class B
  def foo
    self.bar = 1
  end

  private

  def bar=(arg)
    puts "bar= is private method"
  end
end

B.new.foo # => "bar= is private method"

この通りエラーが起きません。
privateメソッドがセッターメソッドの場合には、
明示的にレシーバを書いてもよいのです。

というよりも、明示的にレシーバを書かないとおかしなことになるからです。
以下にそれを示します。

class C
  def foo
    bar = 1 # ただのローカル変数の代入と見なされる
  end

  private

  def bar=(arg)
    puts "bar= is private method"
  end
end

C.new.foo # => 当然何も起こらない

セッターメソッドには明示的なレシーバが必要。
また、それはメソッドがprivateでもpublicでも変わることはありません。

この、privateメソッドのルールの例外については、
Twitterで @n0kada さんに教えていただきました。ありがとうございました。
http://twitter.com/n0kada/status/11625643920

2010年9月7日火曜日

RubyでNoMethodError時にObject#inspectが使われている

RubyKaigi2010での関 将俊さんの発表で聞いた話。

dRubyアプリケーション(RWiki)にirbから接続した時、
メソッド名の打ち間違えをするとなんだかとても遅い。

原因は、NoMethodError時にObject#inspectが走っており、
たくさんのデータ(配列やハッシュに入った数万のオブジェクト)を抱えているオブジェクトを
見えるように印字したらそりゃ遅いよねという話だった。

ruby-1.9.2-p0 > Object.new.does_not_exist_method
NoMethodError: undefined method `does_not_exist_method' for #<Object:0x00000001088288>
 from (irb):1
 from /home/oshow/.rvm/rubies/ruby-1.9.2-p0/bin/irb:17:in `<main>'

ruby-1.9.2-p0 > class Object
ruby-1.9.2-p0 ?>  def inspect
ruby-1.9.2-p0 ?>    "HELLO"
ruby-1.9.2-p0 ?>  end
ruby-1.9.2-p0 ?>end
 => nil 

ruby-1.9.2-p0 > Object.new.does_not_exist_method
NoMethodError: undefined method `does_not_exist_method' for HELLO:Object
 from (irb):7
 from /home/oshow/.rvm/rubies/ruby-1.9.2-p0/bin/irb:17:in `<main>'

確かに、「for #<Object:0x00000001088288>」ってところが
「for HELLO」に変わった。inspectが使われているらしい。

実際にも、関さんはObject#inspectを再定義して遅くなるのを回避したそうです。
(しかたなく…らしい(笑)。発表の動画を見るとわかります)

2010年9月5日日曜日

Ubuntu10.04でデスクトップに流れる音声を録音したい

インターネットラジオや生配信の音声を録音しておきたい、ということがあると思います。
Ubuntuには標準でサウンドレコーダーがついていますが、
ブラウザ等で音声を流しながらこれを使って録音してみても、
そのままでは何も録音することができません。

標準のサウンドレコーダーで録音できるようにするためには、
以下のような作業が必要です。

まず、padevchooserをインストールします。
$ sudo apt-get install padevchooser

次に「アプリケーション」→「サウンドとビデオ」→「PulseAudio Device Chooser」を起動。
パネルの通知領域にアイコンが出てくるので、左クリックして「Manager...」を選択。

Devicesタブの中のSourcesに「alsa_output〜」というものがあるので、
選択して右下の「Properties」ボタンを押す。

出てきたウィンドウの中の、Nameに書かれている内容をコピー。
自分の場合は「alsa_output.pci-0000_00_1b.0.analog-stereo.monitor」でした。

Managerを閉じたら、もう一度パネルのアイコンを左クリックして
「Default Source」→「Other...」を選択。

出てきたウィンドウに先程コピーした内容を張り付けて、OKを押します。


これで、標準のサウンドレコーダーを使って、
デスクトップに流れている音声を録音できるようになりました。


参考: recordMyDesktopでデスクトップを録画してみる on Ubuntu 9.10

(2010年10月25日 追記)

こちらの記事に書かれている方法でもよいようです。

フォロワー