QA@IT

RubyのStruct.newがブロックを受け取るのは、なぜアンドキュメンテッド?

2820 PV

RubyのStruct.newがブロックを受け取るのは、なぜアンドキュメンテッド?

Point = Struct.new(:x, :y)
class Point
  def distance
    Math::sqrt(x**2 + y**2)
  end
end

というのは、

Point = Struct.new(:x, :y) do
  def distance
    Math::sqrt(x**2 + y**2)
  end
end

とも書けると最近気付きました。でも、Struct.newがブロックを受け取って、新たに生成されるクラス ( Point ) のコンテクストで module_eval されるという機能は公式(?)ドキュメントに書かれていません。

調べてみると、この機能は8年ぐらい前からCRubyにあるようですし、Rubiniusにも入っています。ドキュメントに書かれていない理由は何かあるのでしょうか?

  • 避けるべき理由はありますか?
  • ドキュメントにない機能はいつ消えてもおかしくないとか?
  • ふつうに使われてる?

「単に書かれていないだけ(書き忘れ)。他の機能と同様に別に使っても無問題」というなら、それでいいのですが、そういうこともあるものなのかなと疑問に思いました。

回答

詳しくありませんが、興味を持ったので少し調べました。

この機能の発端は[ruby-core:2606]でのDave Thomasさんからの投稿で、「class Struct.newと書きたい」という要望に対して[ruby-core:2607]にてまつもとさんは却下されましたが、代わりに代替案として挙げられた「Struct.newがブロックを受け付けるようにする」案が採用されました(http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=5934 )。実装もまつもとさんです。

上記のやり取りからClassとの一貫性が実装目的ではない(Structの使い勝手向上?)ように見受けられます。

ドキュメントに関しては上記実装時から今日まで特に書かれていません。履歴やソースファイルのannotateを読んでも尽くスルーされてますが、理由はわかりません。ただ、避けるべき機能であれば逆にドキュメントがありそうなものなので(気をつけなければならないので)、実装したしChangeLogも書いたけどソースのコメントには書き忘れたのではないかと個人的に想像してます。

ということで野暮な話ではありますが、この機能に関するドキュメントがない理由は実装者であるまつもとさん(か当時を知る方)にたずねるのが早いと思います。

【追記】まつもとさんによれば、「単にドキュメントに書いてないだけですね」ということでした。
https://twitter.com/yukihiro_matz/status/288219296105132033

編集 履歴 (2)
  • ありがとうございます。興味深いやり取りですね。経緯は分かりました。私の予想も「えっ、そうだっけ。うーん、書き忘れたのかも」ということろです。 -
  • パッチありがとうございました。予想が当たって良かったですw -
  • trunkにドキュメントのパッチが入りました。Ruby 2.0.0には含まれるようですね。
    https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/38883
    -

そのドキュメントに

A Struct is a convenient way to bundle a number of attributes together, using accessor methods, without having to write an explicit class.

とあるように、Structは属性つき匿名クラスを生成するシュガーです。まずこのことを確認しましょう。

> s=Struct.new(:x,:y)
 => #<Class:0x007fc1c18d3418>
> s.class
 => Class

で、ブロックをとるのは、実際にはClassクラスの仕事です。なのでStructクラス自体のドキュメントには言及がないのだと思います。

http://ruby-doc.org/core-1.9.3/Class.html より

fred = Class.new do
  def meth1
    "hello"
  end
  def meth2
    "bye"
  end
end

a = fred.new     #=> #<#<Class:0x100381890>:0x100376b98>
a.meth1          #=> "hello"
a.meth2          #=> "bye"

というわけで、使っても問題なさそうですね。万が一deprecatedの憂き目にあっても、コードの意図は明確ですから、あまり心配いらない気がします。

ついでにいうと、Class.newは継承にも使えるのが結構便利で、

class Error < StandardError; end

Error = Class.new(StandardError)

のように綺麗に(?)書くことができます。

編集 履歴 (0)
  • 回答ありがとうございます。struct.c を見ると、module_eval を呼んでいるのは、やはりStruct#newのようです -
  • そうでしたか。では、Classとの一貫性が理由なんですかね。詳しい人の回答を待ちたいと思います。 -
ウォッチ

この質問への回答やコメントをメールでお知らせします。