Meetup #73
RubyのHashクラス
キーとなる値はhashメソッドとeql?メソッドを持つクラスである必要がある
k1.eql?(k2) == true ==> k1.hash == k2.hash
hash・eql?メソッドはカスタマイズできる
キーが存在しないときに返る値(デフォルト値)もしくはProcを設定できる(default=, default_proc=)
イテレートすると挿入した順序でキー・値を取得できる
Rubyのキーワード引数・キーワードスプラット引数
通常のキーワード引数
code:Ruby
foo(1, a:1)
=>記法を使ったキーワード引数
code:ruby
foo(1, a:1, :b => 100) # => foo(1, a:1, b:100)
キーがシンボルではないキーワード引数
double splat(**)演算子がついたHashになる
code:Ruby
foo(1, a:1, 100 => 100) # => foo(1, **{:a => 1, 100 => 100})
キーがシンボルではないキーワード引数とdouble splat引数の混在
code:Ruby
foo(1, a:1, 100=>100, **{f:42}) # => foo(1, **({:a => 1, 100 => 100}.merge({f:42})))
キーワード引数のキーが全てシンボルの場合は通常のキーワード引数となる
シンボル以外のキーが存在する場合は実行時にHashを生成しdouble splat引数となる
キーワード引数とdouble splat引数の中でキーが重複している場合にはワーニングが出る
monorubyでの実装
indexmap crateを使用する。
RustのHashMap
code:Rust
pub fn insert(&mut self, k: K, v: V) -> Option<V>
where
K: Eq + Hash,
S: BuildHasher,
RubyのHashでは内部からRubyのメソッド(hash eql?)を呼べるようにしておく必要がある
VMのコンテキストを渡す必要あり
Exceptionが返る可能性がある
monoruby用にカスタマイズしたRubyMap RubyEql trait
code:Rust
pub fn insert(&mut self, key: K, value: V, e: &mut E, g: &mut G) -> Result<Option<V>, R>
where
K: Hash + RubyEql<E, G, R>,
S: BuildHasher,
pub trait RubyEql<E, G, R> {
fn eql(&self, other: &Self, e: &mut E, g: &mut G) -> Result<bool, R>;
}
最適化
code:Ruby
def self.new(...)
o = allocate
o.initialize(...)
o
end
これは以下のように変換される
code:Ruby
def self.new(*arg, **kw, &b)
o = allocate
o.initialize(*arg, **kw, &b)
o
end
実際にはキーワード引数が渡されるケースは少ないが、素朴に実装すると毎回Hashを生成する必要がある。
キーワード引数がないときにはとりあえずkwにnilを渡しておき、最後に受け取るメソッドにキーワード残余引数があるときにのみHashを生成するようにした。