ちなみに

火曜日の空は僕を押しつぶした。

Ruby 2.2 で {"Arbitrary Key": "value"} が出来るようになった話

リリース!

さきほど待ちに待った Ruby 2.2.0 がリリースされました。 関係者のみなさん、すばらしいクリスマスプレゼントをありがとうございます!

https://www.ruby-lang.org/en/news/2014/12/25/ruby-2-2-0-released/

変更点については preview1 のときにざっくりとしたまとめを作っているのでご参照ください。

今回は preview1 以降の変更で個人的に一番最高のやつを紹介します。

TL;DR

2.1 までは出来なかった特殊なシンボルでも新しい Hash リテラルの書き方が出来るようになった。

{
  "ヾ(*≧∀≦*)ノ": "寿"
}
#=> {:"ヾ(*≧∀≦*)ノ"=>"寿"}

Hashリテラルの書き方について

Ruby の Hash ではシンボルをキーにすることがよくあります。通常の書き方で書くとハッシュロケット(=>) を使って以下のように書きますが、Ruby 1.9 以降では、専用の書き方が出来るようになりました。

# 通常の書き方
{:key => "value"}

# 1.9以降
{key: "value"}

個人的には新しい書き方の方が好みで、シンボルをキーにしている場合は新しい書き方で統一しています。

しかしながら世の中にはそれをよしとしない人がいて、規約なんかを作るときに対立してしまうことがあります。そういった方の主な主張は「同じハッシュなのでキーによって書き方が違うのは統一性がない」ということで、これについては完璧な反論は難しく、まあその通りというところがありました。

いくつか例を上げると以下のような点が槍玉に上がっており、それぞれ僕の回答を添えました。前提としてリテラルで書く場合であるという条件がつきます。

キーが文字列の場合は新しい書き方が出来ない

どうしても文字列を使わなければいけないシーンは少ない。 シンボルに統一してもあまり困らない。

ただし、Ruby 2.1 以降は文字列を freeze すると同じオブジェクトを指すようになり、ハッシュのキーに文字列を使った場合は自動的に freeze されるようになっていて、パフォーマンス的な差異はなくなりました。

変数をキーとした場合に新しい書き方が出来ない

Hash をリテラルで書くときにキーを変数にしなければならないケースは存在しない。分かりにくいので絶対に止めて欲しい。僕なら以下のように書くと思う。

a = :key1
b = :key2

# ハッシュロケット派の主張
hash = {
  a => "value1",
  b => "value2",
}

# 僕ならこう書く
hash = {}
hash[a] = "value1"
hash[b] = "value2"

好みの問題なのかもしれないけれど、Hash リテラルのキーに変数がくると混乱する。 僕のやり方は Hash リテラルでは常に新しい書き方に統一して、例外的な場合は上記のようにリテラルでは書かないようにすることで区別をつけるようにしています。

複雑なシンボルの場合に新しい書き方が出来ない

これが一番困りました。この場合はどうしようもないため「その通りです」と返すしかなかった。

どういうことかというと以下のようなケースです。

{
  :"symbol-key" => "value",
}

Ruby のシンボルリテラルでは基本的には単語の途中に - などの記号や空白をはさむことが出来ません。そういった特殊なシンボルを作りたいときは :"separated-key" のようにダブルクォートやシングルクォートで囲む必要があります。文字列展開などは通常の文字列と同じルールが適用されます。

前述のとおりこの書き方だと文字列展開が出来るため、シンボルを動的に生成したい場合もクォートする必要があり、僕の場合はそのケースのことを挙げられて、ハッシュロケットに統一する方がよいと言われてしまいました。

Ruby 2.1 までは、このような特殊なシンボルをキーに使おうとした場合、本来ならシンボルなので新しい Hash リテラルの書き方で書くことが出来るところを、ハッシュロケットを使った通常の書き方でしか書くことが出来ませんでした。

そのため、同じシンボルであるはずが統一した書き方が出来ず、ハッシュロケット派に対して強く反論できない元凶となってしまい、むしろ新しい書き方を推進する我々の方が少数派となってしまいがちでした。

ところが!!!!

Ruby 2.2 の NEWS を眺めていると、以下のような記述がありました。( see also: https://bugs.ruby-lang.org/issues/4276 )

  • Hash literal
    • Symbol key followed by a colon can be quoted. [Feature #4276]

つまりクォートして作ったシンボルでも新しい Hash リテラルの書き方が出来るようになったということです!いままでは出来なかったあんなことや、こんなことが出来るように!!

{
  "case-A": true,
  "case-B": false,
}
#=> {:"case-A"=>true, :"case-B"=>false}

names = %w[ taro hanako ]
names.each_with_object({}).with_index(1) do |(name, memo), id|
  memo.merge! "user#{id}": name
end
#=> {:user1=>"taro", :user2=>"hanako"}

これは僕にとっては 2.2 の中でもいちばん嬉しい変更で、これからは胸を張って新しい書き方で統一しますと言えるようになりました。

余談

2.2.0-preview2 の段階では、今回追加された記法で、値として配列([])や Hash ({}) が来るとエラーになっていたのですが、本日公開版ではばっちり直っていました。ありがとうございます!