はじめに
Rubyではシンボルが当たり前のように使用されています。
初めて見たときは今まで触れてきたプログラミング言語にはなかった記法だったため、かなり戸惑いました。
今でも「シンボルって何?」と聞かれると上手く答えられないと感じたので、説明できるように調べて記事に残します。
シンボルとは?
まずは公式リファレンスで確認。
シンボルを表すクラス。シンボルは任意の文字列と一対一に対応するオブジェクトです。文字列の代わりに用いることもできますが、必ずしも文字列と同じ振る舞いをするわけではありません。同じ内容のシンボルはかならず同一のオブジェクトです。
Rubyの内部実装では、メソッド名や変数名、定数名、クラス名などの`名前'を整数で管理しています。これは名前を直接文字列として処理するよりも速度面で有利だからです。そしてその整数をRubyのコード上で表現したものがシンボルです。
文章だけでは分かりにくいですが、見た目は文字列のように見えるけど整数で扱われるオブジェクトということです。
見た目は文字列だけど内部では整数として扱われるので、文字列とは扱いが異なる点もあり、整数ゆえの利点もあるオブジェクトです。
シンボルの表記方法
シンボルは以下のように、:(コロン)文字列
で書かれます。
# シンボル(Symbol)
:user
文字列の場合は以下のように書きますね。
# 文字列(String)
'user'
"user"
公式でも確認した通り、見た目はほぼ文字列と同じに見えます。
ではどういった部分が文字列と異なるのでしょうか。
シンボルと文字列の違い
シンボルと文字列の違いとして、以下の3つがあげられます。
- クラス
- メモリ効率
- 不変性(Immutable)
1. クラス
シンボルも文字列もオブジェクトです。「オブジェクト=インスタンス」であるため、もととなるクラスがあるはずです。
(オブジェクト=インスタンスは意見が分かれそうですが、こちらの記事を参考にオブジェクトとインスタンスは同じだよ派
として発言します)
ではコンソールでシンボルと文字列のクラスを確認してみます。
irb(main):001> :user.class
=> Symbol
irb(main):002> 'user'.class
=> String
オブジェクトの見た目はほぼ同じですが、クラスが違うということが確認できました。
そのため、「シンボルは文字列と見た目が同じだから文字列に使えるメソッドも全部使える!」と勘違いしないように注意が必要です。同じように使えるメソッドもあれば、そうでないメソッドもあります。
両者の公式リファレンスのメソッド部分なども確認してみてください。
# 文字列ではcountは使えるが...
irb(main):003> 'user'.count('s')
=> 1
# シンボルでは使えない
irb(main):004> :user.count('s')
(irb):11:in `<main>': undefined method `count' for an instance of Symbol (NoMethodError)
# 同じように使えるメソッドもある
irb(main):005> 'user'.length
=> 4
irb(main):006> :user.length
=> 4
2. メモリ効率
文字列に比べてシンボルの方がメモリ効率が高く、処理速度が速いという違いがあります。
シンボルの方が処理速度が速い理由として以下の2点があげられます。
- 同じ値でシンボルを作成してもオブジェクトの数が増えないから
- 内部で整数として扱われるから
2-1 値が同じシンボルを作成してもオブジェクトの数が増えない
文字列は同じ値で生成したとしても、毎回新しいオブジェクトを生成します。そしてオブジェクトはメモリ上に存在するため、毎回新しいオブジェクトが生成されると、その分メモリを圧迫します。
しかしシンボルの場合は、同じ値で生成すると 毎回同一のオブジェクトが生成され、オブジェクトの数が増えません。 そのためメモリの圧迫も文字列に比べて少なく、効率も高くなります。
確認のため、SymbolオブジェクトとStringオブジェクトのobject_id
を見てみましょう。
シンボルのobject_idは同一ということが分かります。
object_idとは
オブジェクト固有のIDのことで、オブジェクト生成時に発番されます。 オブジェクトの同一性をあらわしてくれます。# シンボルはobject_idが全て同じ
irb(main):007> :user.object_id
=> 1588508
irb(main):008> :user.object_id
=> 1588508
irb(main):009> :user.object_id
=> 1588508
# 文字列はobject_idが毎回違う
irb(main):010> 'user'.object_id
=> 37640
irb(main):011> 'user'.object_id
=> 38780
irb(main):012> 'user'.object_id
=> 39920
------------------------------------------------------------
# 違う値のシンボルでも同様
irb(main):013> :users.object_id
=> 7472988
irb(main):014> :users.object_id
=> 7472988
2-2 内部で整数として扱われる
シンボルは内部で整数として扱われるため、比較検索処理の場合は文字列よりも速く処理を行うことができます。
文字列同士を比較したり、検索にかけるよりも整数で比較・検索を行う方が処理の回数が少ないようです。
そのため、整数として扱われるシンボルの方が処理を速く行えます。
↓のQ&A記事を見て頂く方が分かりやすいと思います...!
3. 不変性(Immutable)
シンボルはImmutableなため、変更不可という特性を持っています。反対に文字列はmutableであるため変更可能です。
そのため、シンボルに対して破壊的メソッドを使用することは不可能ということが分かります。
破壊的な変更がされないことが保証されるため、値を変えられることがないというメリットがあります。
# シンボル
irb(main):015> symbol = :user
=> :user
# 値を変えることはできない
irb(main):016> symbol.upcase!
(irb):16:in `<main>': undefined method `upcase!' for an instance of Symbol (NoMethodError)'
# 破壊的でなければ使用できる
irb(main):017> symbol.upcase
=> :USER
# 文字列
irb(main):018> string = 'user'
=> "user"
# 破壊的に変更できる
irb(main):019> string.upcase!
=> "USER"
シンボルの主な用途
シンボルは主に以下の用途に使用されます。
- ハッシュのキー
- ステータスの指定
- メソッド名/変数名/定数名/クラス名
1. ハッシュのキーとしての利用
Rubyでハッシュを利用する際、キーとバリューを設定するはずです。
このキー部分をシンボルとして使用することが多く、シンボルにすることで見た目もシンプルになり、パフォーマンスも高くなります。
# キーを文字列に
irb(main):020> user = {"first_name" => "Tarou", "last_name" => "Tanaka"}
=> {"first_name"=>"Tarou", "last_name"=>"Tanaka"}
# キーをシンボルに
irb(main):022> user = {first_name: "Hanako", last_name: "Yamada" }
=> {:first_name=>"Hanako", :last_name=>"Yamada"}
2. ステータスの指定
シンボルはラベルやステータス名のような固定された値(権限レベル、ユーザなどの状態)を扱う場合に適しています。
公式リファレンスの用途
にも以下のように記載されていました。
実用面では、シンボルは文字の意味を明確にします。`名前'を指し示す時など、文字列そのものが必要なわけではない時に用います。
シンボルは文字列に比べて効率的かつImmutableというメリットがありました。
そのため、操作や加工を必要とせず、文字列そのものではなく、名前
が必要になるラベルやステータスとして用いられることが多いです。
補足:文字列が適している場合は??
文字列が具体的なデータとして必要な時
文字列自体が具体的なデータそのもの(名前、文章、メールアドレスなど)で、操作や加工が必要になる場合は文字列を使用するようにします。
# ユーザー名を設定
user_name = "Tarou Yamada "
p user_name #=> "Tarou Yamada "
# 不要なスペースを削除したい
trimmed_name = user_name.strip
p trimmed_name #=> "Tarou Yamada"
# すべての文字を大文字に変換したい
upcase_name = trimmed_name.upcase
p upcase_name #=> "TAROU YAMADA"
# 名前を区切り、苗字と名前に分ける
name_parts = trimmed_name.split
p "First Name: #{parts[0]}, Last Name: #{parts[1]}"
#=> "First Name: Tarou, Last Name: Yamada"
3. メソッド名/変数名/定数名/クラス名としての利用
シンボルとは?の説明で公式から以下を抜粋しました。
Rubyの内部実装では、メソッド名や変数名、定数名、クラス名などの`名前'を整数で管理しています。これは名前を直接文字列として処理するよりも速度面で有利だからです。そしてその整数をRubyのコード上で表現したものがシンボルです。
定義したメソッドや変数などは内部で整数として管理されていて、実装側では人間が理解しやすいシンボルとして呼び出すことが可能ということです。
処理中にメソッド名などを加工することは無いと思われるので、文字列よりもシンボルを使用するほうが効率的です。
実際にメソッド/変数/定数/クラスを作成してシンボルができているか確認できます。
class User
end
def create_user
end
user_age = 33
UserNumber = 1
# 定義したシンボルオブジェクトを配列で返してくれる
p Symbol.all_symbols #=> [..., :User, :create_user, :user_age, :UserNumber, ...]
# シンボルが多い場合、絞り込むと見やすい
p Symbol.all_symbols.select{|sym|sym.to_s.eql? 'User'}
#=> [:User]
p Symbol.all_symbols.select{|sym|sym.to_s.eql? 'create_user'}
#=> [:create_user]
p Symbol.all_symbols.select{|sym|sym.to_s.eql? 'user_age'}
#=> [:user_age]
p Symbol.all_symbols.select{|sym|sym.to_s.eql? 'UserNumber'}
#=> [:UserNumber]
まとめ
シンボルってなに?
- 文字列の見た目をした整数
どんな時に使う?
- ハッシュのキー、メソッド名/変数名/定数名/クラス名、ラベルやステータスなどのデータそのものではなく、データの名前に使用すると◎
なんでシンボルを使うの?
- 中身は整数であるため、比較や検索処理を行う時は文字列を使用するよりも処理速度が速いから
- 同じ値のシンボルは同一オブジェクトになるため、文字列よりもメモリを圧迫しないから
- Immutable(不変)なので勝手に値を変えられる心配がないから。値を変えたくない場合に使える
おわりに
今回のアウトプットを通して、シンボルとは何か?文字列と似てるけどどこが違ってどう便利なのか、理解が深まったと感じます。
しかし、内部で整数として扱われるから文字列よりも比較処理が速い
という部分については腹落ちしていないのでもっとコンピュータの内部について学ぶべきなのかなと感じています。(なんで整数だと早い?コンピュータは文字列だと文字列→整数に変換する手間がかかるから?では文字列の比較は内部ではどのような手順で処理されているんだ?)
長くなりましたがここまで読んでいただきたありがとうございました🙇
間違っている箇所などあれば教えていただけると幸いです。