[Ruby] シンボルとハッシュ
TL;DR
- シンボルの特徴
- 同じ内容のシンボルはかならず同一のオブジェクト
- ソースコード上では文字列のように見え、内部では整数として扱われる
- キーがシンボルの場合、
{a => b}
は{a: b}
と書き換えることができる
概要
単に自分が Ruby の newbie なのかもしれないが、Railsでシンボルを使ってハッシュを表現するときにどのように記述すべきか迷うことがある。Hashのキーに文字列を使うかシンボルを使うかとか、「=>」(ハッシュロケット、ファットアローなどともいう)を使うか使わないかなどである。
{"key" => "value"} {:key => "value"} {:key => :value} {key: "value"} {key: :value}
がどう違うかを説明できるようになって平昌オリンピックを迎えたい。
以下、特に指定がなければ Ruby 2.5.0(2017/12/28現在 最新版)での話とする。
シンボルとは「名前という概念を表すオブジェクト」
シンボル(Symbol)は正しくは Symbolクラスのオブジェクト。シンボルオブジェクトは、:symbol
のようなリテラルで表現することができる。
マニュアルを要約すると、
- ソースコード上では文字列のように見え、内部では整数として扱われる、両者を仲立ちするような存在
- 同じ内容のシンボルはかならず同一のオブジェクトになる
ということである。一言で言うと「名前という概念を表す整数オブジェクト」というところか。
同じ内容のシンボルはかならず同一のオブジェクト(同一のobject_id)
マニュアルによれば、「同じ内容のシンボルはかならず同一のオブジェクト」 ということである。これを実際に確認してみる。文字列の場合と比較するため、同じ内容のシンボルの場合と同じ内容の文字列の場合でそれぞれ比較する。
sym1 = :symbol sym2 = :symbol str1 = "string" str2 = "string" p sym1.object_id p sym2.object_id p str1.object_id p str2.object_id
[実行結果] # 同じ内容のシンボルは同一のオブジェクトになる(=object_idが同一) 384488 384488 # 同じ内容の文字列は異なるオブジェクトになる(=object_idが異なる) 70167279378560 70167279378540
この結果から、シンボルは、プログラム上では文字列にように表現しているが、内部では整数(object_id)として扱われていると言える。ここがマニュアルでいう ソース上では文字列のように見え、内部では整数として扱われる、両者を仲立ちするような存在 につながる。だからメモリ効率もよいし、(整数比較となるので)比較が高速になるということなのだろう。
シンボルのシンタックスシュガー
シンボルオブジェクトは、以下のようなリテラルでも取得することができる。以下は全て :symbol というシンボルになり全て同じ値になる(記法の違いのことをシンタックスシュガーと呼ぶ)。
p :symbol p :"symbol" p "symbol".to_sym p %s(symbol) # %記法
[実行結果] :symbol :symbol :symbol :symbol
シンボルを使うとよいケース
「同じ内容のシンボルはかならず同一のオブジェクト」 という特徴を利用すると、複数あるとよろしくないものを作るときに向いている。例えば、ハッシュのキーとしてシンボルを使うことでキーに一意性を持たせることができる。
また、「ソースコード上では文字列のように見え、内部では整数として扱われる」 という特徴を利用すると、C言語の列挙型(enum)のように、プログラムからは名前でアクセスする整数リスト(値は別に何であっても構わない)の使い方が想像できる。
Railsでは、ActiveRecord::Enum
を使って列挙型を表現することができる。
class Membership < ActiveRecord::Base enum gender: [:female, :male, :other, :rather_not_say] end
ハッシュアローを使うとき、使わないとき
マニュアルのHashクラスを見るとこう書かれている。
{a => b, ... } # aはキー、bは値となる
{s: b , ... } # { :s => b, ... } と同じ。キーがシンボルの場合の省略した書き方
ということは、下記の2つは記法の違いなので、ハッシュとしては同一となる。
{:key => "value"} {key: "value"}
また、下記も記法の違いで同一である。
{:key => :value} {key: :value}
念のため下記のサンプルコードで確かめてみる。
h1 = {"key" => "value"} h2 = {:key => "value"} h3 = {:key => :value} h4 = {key: "value"} h5 = {key: :value} p h1 === h2 # false p h1 === h3 # false p h1 === h4 # false p h1 === h5 # false p h2 === h3 # false p h2 === h4 # true p h2 === h5 # false p h3 === h4 # false p h3 === h5 # true p h4 === h5 # false
{:key => "value"}
と {key: "value"}
、そして {:key => :value}
と {key: :value}
が同じであることが確認できた。
まとめると、キーがシンボルの場合に限り、=> (ハッシュロケット)記法以外に、 : 記法(シンボルを表す前コロンは書かず、キーと値の間にコロンを置く)で表現できる。いいかえると、キーがシンボルの場合 {a => b}
は {a: b}
と書き換えることができる ということだ。
Hashのキーがシンボルでも文字列でもアクセスできる
rubyのハッシュは、下記のサンプルの通り、キーがシンボルか文字列かを判別する。
hash = {key: "value"} hash[:key] #=> "value" hash["key"] #=> nil hash = {"key" => "value"} hash[:key] #=> nil hash["key"] #=> "value"
だけど、Rails の Controller において リクエスト情報(クエリパラメータやPOSTで送られてくるデータ)が格納される params については、シンボルと文字列のどちらを指定しても同じ値が取得できる。
class ExamplesController < ApplicationController def index p params[:action] # => 'index' p params['action'] # => 'index' end end
これは、前回のエントリとど同様、Railsのコア拡張(ActiveSupport::CoreExtenstions::Hash)に含まれる、Hash#with_indifferent_access
の効果である。
中身としては比較的単純で、キーがシンボルだったら文字列に変換するようにしてあるだけだ。
参考までに、キーがシンボルでも文字列でもアクセスできる、Hashクラスを継承したHash2クラスの疑似コードを書く。
# Hash2クラス class Hash2 < Hash def convert_key(key) key.kind_of?(Symbol) ? key.to_s : key end def [](key) super(convert_key(key)) end def initialize(constructor = {}) if constructor.respond_to?(:to_hash) super() update(constructor) hash = constructor.to_hash self.default = hash.default if hash.default self.default_proc = hash.default_proc if hash.default_proc else super(constructor) end end end
# Hash2を使ったテスト h = Hash2.new("key" => "value") p h["key"] # => 'value' p h[:key] # => 'value'
TL;DR(再掲)
- シンボルの特徴
- 同じ内容のシンボルはかならず同一のオブジェクト
- ソースコード上では文字列のように見え、内部では整数として扱われる
- キーがシンボルの場合、
{a => b}
は{a: b}
と書き換えることができる - 2017年も終わり、良いお年を!