Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hash メソッドが載っていないクラスがある #2556

Open
scivola opened this issue May 2, 2021 · 8 comments
Open

hash メソッドが載っていないクラスがある #2556

scivola opened this issue May 2, 2021 · 8 comments

Comments

@scivola
Copy link
Contributor

scivola commented May 2, 2021

Numeric,Integer,Symbol などには hash メソッドが載っていません。
(Float,String,Array などには載っています)

@universato
Copy link
Contributor

universato commented May 2, 2021

結論からいうと、載ってなくていいように思います。

ただ、IntegerStringなど特別にそのクラスに何か書くのは説明としてありなのかなと思っています。
(追記: 細かいですが、ここは、Stringじゃなくて、Symbolと書くはずでした。Stringも特別でしたが)

るりまの Object#hash の項目による確認

Object#hash (Ruby 3.0.0 リファレンスマニュアル)

ただし、Fixnum, Symbol, String だけは組込みのハッシュ関数が使用されます(これを変えることはできません)。

この引用にはまだPRが反映されてなくFixnumと書いてありますが、他のクラスならhashが呼ばれる局面でも、IntegerSymbolは内部に組み込まれたハッシュ関数が使われているようです。

仮にRubyユーザーが再定義したとしても内部で定義された関数が呼ばれ、ユーザーが独自に再定義する意味も特別なさそうです。

methodメソッドによる確認

IntegerSymbolなどはhashが独自に定義されてないことが伺えます。

p 1.method(:hash)    # => #<Method: Integer(Kernel)#hash()>
p :sym.method(:hash) # => #<Method: Symbol(Kernel)#hash()>

p (1.0).method(:hash) # => #<Method: Float#hash()>
p [].method(:hash)    # => #<Method: Array#hash()>

@scivola
Copy link
Contributor Author

scivola commented May 2, 2021

Integer,Symbol は特別なので書くべきだと思います。
既に書かれている String も特別であることを追記すべきだと思います。
Numeric はおそらく独自の実装は持っていないので書く必要は無いと思いますが,「継承しているメソッド」として Object#hash が載っていないのはおかしいのではないでしょうか。

@osyo-manga
Copy link
Contributor

ちなみに RDoc には Symbol#hashInteger#hash , Numeric#hash のドキュメントは書かれていませんね。

逆に String#hashArray#hash などはドキュメントが書かれています。

@universato
Copy link
Contributor

universato commented May 2, 2021

自分で引用しておきながらよく読めてなかったですが、Stringも特別だったんですね。IntegerSymbolはRubyメソッドとしてhashが再定義されてないのに、Stringの方ではhashが再定義されているようで不思議です。

p "".method(:hash)  # => #<Method: String#hash()>

IntegerやSymbolでのhashの項目・説明について

ここは、自分の意見の補強です。

う〜ん、再定義もされてない継承してるだけのメソッドは、基本的にそのクラスのインスタンスメソッドとして説明があるわけではないし、(再定義も含んで)定義されているかどうかでメソッドの説明を載せるかどうかになっていると、わかりやすい線引でよさそうと思っていました(実際のるりまは、ここに例外もありますが)。
また、IntegerSymbolも確かに特別だから、何かそのクラスに説明があってもいいのかもしれませんが、それでもやはりInteger#hashSymbol#hashが再定義されているわけでもないし、普通ユーザーが直接使うわけでも再定義するところでもないメソッドだと思うので、他の一般的なメソッドと混じったらよくないのではという懸念がありました。

しかし、Stringは項目としてあるし特別である旨を追記してあってもいいと考えますが、(背景を知らずに)IntegerSymbolにその説明がなかったら「なんでやねん」と思う感じがする気もします。

NumericでのObject#hashについて

以下の観点から、NumericObject#hashが載ってないのは、おかしくないように思います。

  • Numericクラスの説明で、継承しているメソッドは「Comparableから継承しているメソッド」と明記されている。
  • 自分の知る限りでは、どのクラスもObjectから継承しているメソッド一覧は存在しない。
    • 冒頭でObjectから継承している事実だけが書かれている。
  • 普通は、ユーザーがNumerichashメソッドを再定義するようなことも直接使うこともない。
  • Numerichashに関して、IntegerStringSymbolのような特別なことはなさそう。

NumericObject#hashだけが何か枠から外れているということはないと思っています。

もしすべてのクラスにObjectから継承している全てのメソッドを一覧にだすべきという話なら、るりまのユーザーの邪魔にならないかなという懸念の方が強いです。
ただ、この懸念に対しては、折りたたみでメソッド名のリンクだけにするとかなら、この懸念は解消されそうです。

@osyo-manga
Copy link
Contributor

Integer,Symbol は特別なので書くべきだと思います。

すみません、ここで言う『特別なので』とはどういう事を指しているのでしょうか。
るりま全体的に言えるのですが各クラスの説明が書かれているメソッドは『そのクラスで再定義されているもの』に限られているようにみえます(もしその限りでなかったらすみません。
今回の Intger#hashSymbol#hash@universato さんがおっしゃられているように IntgerSymbol では再定義されておらず Object#hash のものをそのまま使用しています。
@universato さんもおっしゃられているように再定義されていないメソッドをわざわざサブクラスでドキュメントを追加する必要はないように感じます。
ただし、 継承しているメソッド のような項目に何かしらのリンクを貼るのはよいとは思います :)

@universato
Copy link
Contributor

universato commented May 2, 2021

自分が「特別」の意味で使っていたのは、るりまのObject#hashの説明にあるように、ユーザーがhashメソッドを再定義しても意味がなさないことを指してました。

Object#hash (Ruby 3.0.0 リファレンスマニュアル)

ただし、Fixnum, Symbol, String だけは組込みのハッシュ関数が使用されます(これを変えることはできません)。

具体的には

一般的なクラスの場合

具体的には、普通、一般的なクラスだと、hashメソッド等を再定義することで、HashのキーやArray#uniqなどの操作による同一性を変更することができます。

class Foo end
p({ Foo.new => 1, Foo.new => 2 }) # => {#<Foo:0x00000000011d5118>=>1, #<Foo:0x00000000011d50c8>=>2}
p([Foo.new, Foo.new].uniq)        # => [#<Foo:0x00000000011d4bc8>, #<Foo:0x00000000011d4ba0>]

#再定義しなおすと、同一なものと判定するように変更できる
class Foo
  def eql?(obj); true end
  def hash; 0 end
end
p({ Foo.new => 1, Foo.new => 2 }) # => {#<Foo:0x00000000026991a8>=>2}
p([Foo.new, Foo.new].uniq)        # => [#<Foo:0x0000000002698d20>]

Integer, Symbol, Stringの特別なクラスの場合

class Integer
  def eql?(obj); raise end
  def hash; raise ;end
end

p({ 1 => 1, 2 => 2 })  # => {1=>1, 2=>2}
p([1, 2].uniq)         # => [1, 2]

Integerではエラーを起こすように再定義しても結果は変わらず、hashメソッド等が呼ばれてないことがわかります。

@universato
Copy link
Contributor

https://github.com/ruby/ruby/blob/121fa24a3451b45c41ac0a661b64e9fc8600e589/hash.c#L191-L231
ちなみに、Ruby側の実装を見たら、
Float, true, false, nilHashのキーの比較で組み込み関数が使われていました。
特別なのは3つだけではなかったです。

https://github.com/ruby/ruby/blob/fb04c69418ceee696a114fe31279cf3a5ea16d30/array.c#L5115
その他、参考に、Ruby側の実装では、Arrayhashを求める際には、要素に対して必ずRubyで定義されたhashメソッドが呼ばれるようです。他に、Complex, Rational, Rangeなどのhashを求める際も同様でした。

@universato
Copy link
Contributor

あと、Integerhashメソッドの記載がなくて、Floathashメソッドの記載があるという話ですが、
初見でリファレンスマニュアルを見てたときにhashだけ説明が少なくそのクラス特有のメソッドらしさがなく、使い方がわからず、項目として列挙されていることに違和感を抱いた記憶があります。

特にRationalがわかりにくいように思います。(今は再定義されてるから、記載があるのだろう、とは思える)
Image from Gyazo

再定義されている事情にもよると思いますが、

  • Objectで既に定義されているメソッドを個別のクラスで説明するのが本当にわかりやすいのか
    • 説明するにしても、新しく定義したメソッドと再定義されたメソッドを一緒くたに並べてわかりやすいのか

のような疑問を改めて感じます。
既存の部分のわかりやすさに疑問を感じているため、個別のクラスに何か説明をつけることになっても、そのあたりに配慮があれば嬉しいと思います。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants