hidakatsuya.dev hidakatsuya

Ruby の Hash#delete はブロックを渡すことができる

Hash#delete はブロックを受け取ることを初めて知った。そして、これがとても便利だったのでまとめておく。

Hash#delete は、

h = { a: 'aaa', b: 'bbb' }

h.delete(:a) #=> 'aaa'
h.delete(:nonexistent_key) #=> nil

p h #=> { b: 'bbb' }

のように、指定したキーを取り除くことができるメソッドで、

という特徴を持っている。

そして、ブロックを渡した場合、

h.delete(:x) { |key| "#{key} は見つかりませんでした" } #=> ":x は見つかりませんでした。"

のように、指定したキーが見つからなかった場合にブロックが評価され、その結果が戻り値となる。

キーが見つからなかった場合の戻り値についてまとめると、

となる。

さて、これの何が便利だったかというと、つい最近 prawn-emoji のこの部分 の実装をするときに、この仕様のおかげでシンプルに書くことができた。

# == Additional Options
# <tt>:emoji</tt>:: <tt>boolean</tt>. Whether or not to draw an emoji [true]
def draw_text!(text, options)
  draw_emoji = options.delete(:emoji) { true }

 if draw_emoji && Emoji::Drawer.drawable?(text)
   emoji_drawer.draw(text.to_s, options)
 else
   super
 end
end

このコードは、既存の draw_text!を拡張する実装である。L#4 で Hash#delete を使っており、

draw_emoji = options.delete(:emoji) { true }

ということをこの一行で実現することができる。

:emoji option の仕様について少し補足しておくと、

  1. :emoji は拡張した option なので、L#10 の superoptions を渡す際に削除しておきたい
  2. :emoji はデフォルト true にしたい

という背景があった。

そして、元々、この部分(L#4)は、Hash#fetch(key, default) を使って実装していたが、(1) の要件を満たすためには、 どうしても別途 :emoji キーを消すコードを入れないといけなかった。

# == Additional Options
# <tt>:emoji</tt>:: <tt>boolean</tt>. Whether or not to draw an emoji [true]
def draw_text!(text, options)
  draw_emoji = options.fetch(:emoji, true) #=> Hash#fetch を使った場合
  options.delete(:emoji)                   #=> 別途 :emoji キーを削除しないといけない

 if draw_emoji && Emoji::Drawer.drawable?(text)
   emoji_drawer.draw(text.to_s, options)
 else
   super
 end
end

細かいことだが、Hash#delete を使うとこれをスッキリ書くことができる (読みやすいかどうかは別)

See also Ruby2.7.0 リファレンスマニュアル > Hash クラス

ちなみに、

Hash#delete の "取り除いたキーの値を返す" という特徴を使って、

draw_emoji = options.delete(:emoji) || true

という方法も考えられるが、これは正しく動作しない。emoji: false のとき、draw_emoji は true になってしまうからだ。