Edited at

Ruby 基本のき: オブジェクト周りを遊びながら理解する

More than 3 years have passed since last update.


0. はじめに

C/C++, C#, Java, Perl, PHP, Python など他言語の経験者が Ruby に触れる際の導入として、

オブジェクト周りを中心に Ruby の基本をざっくり理解するための資料です。

irb (Interactive Ruby) という対話的実行環境コマンドを使って実際に遊びながら読み進めれば、

1時間くらいで Ruby の特徴や感触が掴めると思います。

なお、Ruby のインストール方法や文法・ライブラリの網羅などはここでは扱いません。

それらを知りたい場合は以下の公式ドキュメントが参考になります。

https://www.ruby-lang.org/ja/documentation/


1. Ruby ではすべてがオブジェクト

Ruby にはいわゆる基本データ型(プリミティブ型)がありません。

整数・浮動小数点数・文字列・配列・連想配列・truefalsenil など、

すべてがオブジェクト(特定クラスのインスタンス)になります。

# 整数

irb> 1.class
=> Fixnum

# 浮動小数点数
irb> 1.0.class
=> Float

# 文字列
irb> "abc".class
=> String

# 配列
irb> [1, 2, 3].class
=> Array

# 連想配列
irb> {"key" => "value"}.class
=> Hash

# true
irb> true.class
=> TrueClass

# false
irb> false.class
=> FalseClass

# nil(他言語でいう null)
irb> nil.class
=> NilClass

オブジェクトである以上、メソッドが使えます。


  • 例えば 1.next など、クラス毎に様々なメソッドが定義されています

  • 加算 1 + 1+ ですらメソッドです(これは 1.+(1) のシンタックスシュガーです)

  • 配列参照 ary[0][] もメソッドです(arr.[](0) のシンタックスシュガーです)

  • 配列代入 ary[0] = 1[]= もメソッドです(arr.[]=(0, 1) のシンタックスシュガーです)


2. Ruby ではすべてのクラスが再定義可能

Ruby では組み込みクラスを含むすべてのクラスが再定義可能です。

既存クラスを再定義(再オープン)することで、メソッドの追加・上書きなどが容易にできます。

例えば以下のようなことが可能です。

irb> class Fixnum

irb> def megabytes
irb> self * 1024 * 1024
irb> end
irb> end
=> :megabytes

irb> 1.megabytes
=> 1048576

さらに、以下のようなことも可能です。(良い子はマネしちゃダメ!)

irb> class Fixnum

irb> def +(other)
irb> self - other
irb> end
irb> end
=> :+

irb> 1 + 2
=> -1


3. Ruby の基本オブジェクトで遊ぶ


3.1 整数(Fixnum オブジェクト)

irb> 1 + 1

=> 2

irb> 2 * 8
=> 16

irb> 2 ** 16
=> 65536

irb> 2.next
=> 3

irb> 2.next.next.next # このように「メソッドチェイン」も可能
=> 5


3.2 浮動小数点数(Float オブジェクト)

irb> 1.5 + 1

=> 2.5

irb> 2.5 * 8
=> 20.0

irb> 1.6.floor
=> 1

irb> 1.6.round
=> 2


3.3 文字列(String オブジェクト)

irb> "abc" + "d"

=> "abcd"

irb> "bye" * 2
=> "byebye"

irb> "abc".upcase
=> "ABC"

irb> "abc".next
=> "abd"

irb> "abc".next.upcase.reverse
=> "DBA"


3.4 配列(Array オブジェクト)

irb> ary = [1, 2, 3]

=> [1, 2, 3]

irb> ary[0]
=> 1

irb> ary[1]
=> 2

irb> ary[2]
=> 3

irb> ary[-1]
=> 3 # -nは後ろからn番目の要素を参照する

irb> ary[4]
=> nil # 範囲外の要素を参照してもエラーにならない

irb> ary[9] = 10
=> 10
irb> ary
=> [1, 2, 3, nil, nil, nil, nil, nil, nil, 10] # 歯抜けの要素は nil でパディングされる


3.5 連想配列(Hash オブジェクト)

irb> hash = {"key" => "value"}

=> {"key" => "value"}

irb> hash["key"]
=> "value"

irb> hash["hoge"]
=> nil # 未定義のキーで参照してもエラーにならない

irb> hash["foo"] = "bar"
=> "bar"
irb> hash
=> {"key" => "value", "foo" => "bar"}

上記の例では Hash のキーに文字列(String オブジェクト)を使っていますが、

シンボル(Symbol オブジェクト)を使うことも多いです。

詳細は 5.3 文字列とシンボルは違うモノ(Hash のキー利用時の注意点) を参照してください。


3.6 オブジェクトの変換メソッド(to_*)

Ruby の各クラスには to_i, to_f, to_s, to_a, to_h といった型変換的なメソッドが定義されています。

これらの定義有無はクラスや Ruby のバージョンによってバラツキがあります。


Fixnum

# -> Fixnum

irb> 1.to_i # 自分自身を返す
=> 1

# -> Float
irb> 1.to_f
=> 1.0

# -> String
irb> 1.to_s
=> "1"



Float

# -> Fixnum

irb> 3.99.to_i # 小数点以下切り捨て
=> 3

# -> Float
irb> 1.0.to_f # 自分自身を返す
=> 1.0

# -> String
irb> 1.0.to_s
=> "1.0"
irb> (1.0 / 0.0).to_s
=> "Infinity"



String

# -> Fixnum

irb> "1".to_i
=> 1
irb> "3.99".to_i # 小数点以下切り捨て
=> 3
irb> "foo".to_i # 非数字文字列ではエラーにならず0を返す
=> 0
irb> "".to_i # 空文字でも0を返す
=> 0
irb> "12foo".to_i # 頭に数字が含まれる場合は解釈できるところまでを変換対象とする
=> 12

# -> Float
irb> "1".to_f
=> 1.0
irb> "3.99".to_f
=> 3.99
irb> "foo".to_f # 非数字文字列ではエラーにならず0.0を返す
=> 0.0
irb> "".to_f # 空文字でも0.0を返す
=> 0.0
irb> "12foo".to_f # 数字が含まれる場合は解釈できるところまでを変換対象とする
=> 12.0

# -> String
irb> "foo".to_s
=> "foo" # 自分自身を返す



Array

# -> Array

irb> [1, 2, 3].to_a
=> [1, 2, 3] # 自分自身を返す

# -> Hash
irb> [[1, 2], ["foo", "bar"]].to_h # Ruby 2.1 以降で利用可能
=> {1 => 2, "foo" => "bar"}



Hash

# -> Array

irb> {1 => 2, "foo" => "bar"}.to_a
=> [[1, 2], ["foo", "bar"]]

# -> Hash
irb> {1 => 2, "foo" => "bar"}.to_h
=> {1 => 2, "foo" => "bar"} # 自分自身を返す


nilNilClass オブジェクト)の変換メソッドは0や空を表すオブジェクトを返します。


NilClass

# -> Fixnum

irb> nil.to_i
=> 0

# -> Float
irb> nil.to_f
=> 0.0

# -> String
irb> nil.to_s
=> ""

# -> Array
irb> nil.to_a
=> []

# -> Hash
irb> nil.to_h
=> {}



4. Ruby でのオブジェクトの扱われ方(メモリ内部イメージで理解する)

Ruby でのオブジェクト生成・参照について、メモリ内部のイメージ図と共に具体例で説明します。

例1:

str = "foo"

=> "foo"
ary = [str, str]
=> ["foo", "foo"] # ① これは予想通りの結果

str = "bar"
=> "bar"
ary
=> ["foo", "foo"] # ② なぜ ["bar", "bar"] にならない!?

なぜなら、メモリ内部はこのようになっているから:

example_1.png

例2:

irb> str = "foo"

=> "foo"
irb> ary = [str, str]
=> ["foo", "foo"] # ① これは予想通りの結果

irb> str << "bar"
=> "foobar"
irb> ary
=> ["foobar", "foobar"] # ② これも予想通りの結果

irb> str = str + "baz"
=> "foobarbaz"
irb> ary
=> ["foobar", "foobar"] # ③ なぜ ["foobarbaz", "foobarbaz"] にならない!?

なぜなら、メモリ内部はこのようになっているから:

example_2.png


4.1 破壊的メソッドと非破壊的メソッド

自分自身を変更するメソッドが「破壊的メソッド」です。

自分自身を変更しないメソッドが「非破壊的メソッド」です。

先の例では String クラスの << が破壊的メソッド、+ が非破壊的メソッドです。

Ruby では最後に ! の有無だけが異なる同名メソッドをしばしば見かけます。

その場合は基本的に ! 有りが破壊的メソッド、! 無しが非破壊的メソッドになります。

# upcase!(破壊的メソッド)

irb> str1 = "abc"
=> "abc"
irb> str2 = str1.upcase!
=> "ABC"
irb> str1
=> "ABC" # str1 が変更されている(さらに言うと、str1, str2 は同一オブジェクトを参照している)

# upcase(非破壊的メソッド)

irb> str1 = "abc"
=> "abc"
irb> str2 = str1.upcase
=> "ABC"
irb> str1
=> "abc" # str1 は変更されていない(さらに言うと、str1, str2 は異なるオブジェクトを参照している)

ただし、以下のように ! が無い破壊的メソッドも存在します。



  • String クラスの <<


  • Array クラスの shift


  • Hash クラスの update


4.2 mutable なオブジェクトと immutable なオブジェクト

破壊的メソッドがあるのが「mutable(変更可能)なオブジェクト」です。

破壊的メソッドがないのが「immutable(変更不可能)なオブジェクト」です。

これまでに出てきたオブジェクトの mutable / immutable は以下の通りです。


  • mutable: 文字列・配列・連想配列

  • immutable: 整数・浮動小数点数・truefalsenil


5. その他、知らないとハマるかもしれないあれこれ


5.1 Ruby での真偽判定

式の評価結果が nil または false の場合のみで、それ以外のオブジェクトではすべてとなります。

0 でも真というのはハマりやすいポイントなので注意です。

irb> def check(value)

irb> value ? true : false
irb> end
=> :check

# 偽判定になるパターン
irb> check(nil)
=> false
irb> check(false)
=> false

# 真判定になるパターン
irb> check(0)
=> true
irb> check(0.0)
=> true
irb> check("0")
=> true
irb> check("nil")
=> true
irb> check("false")
=> true
irb> check("")
=> true
irb> check([])
=> true
irb> check({})
=> true

なお、Ruby において ==, <, <=, =>, > などは各クラスで定義されているメソッドです。

そのため、比較内容はクラスによって異なります。(クラスによってはメソッド自体が定義されていない場合もあります)


5.2 Ruby には ++, -- が無い

Ruby では整数(Fixnum オブジェクト)が「immutable なオブジェクト」として設計されています。

そのため、++, -- のように自分自身を破壊的に増減させる術はありません。

その代わりに i += 1, i -= 1 を使います。


5.3 文字列とシンボルは違うモノ(Hash のキー利用時の注意点)

例えば文字列 "foo" とシンボル :foo は属しているクラスが異なるので、全く違うオブジェクトです。



  • "foo" は mutable な String オブジェクト


  • :foo は immutable な Symbol オブジェクト

これを混同すると、Hash での値取得でハマることになります。

irb> hash = {'key' => 'value'} # 文字列でキーを定義

=> {'key' => 'value'}

irb> hash['key']
=> 'value' # 文字列では値を取得できる
irb> hash[:key]
=> nil # シンボルでは値を取得できない(:key というキーは未定義だから)

なお、文字列とシンボルはそれぞれ to_sym, to_s メソッドで相互に変換可能です。

irb> "foo".to_sym

=> :foo

irb> :foo.to_s
=> "foo"

これを利用して、Rails(Webアプリケーションフレームワーク)の環境下では HashWithIndifferentAccess という Hash の拡張クラスにより文字列とシンボルのどちらを使っても値を取得できるようになっています。

このため、Rails から Ruby を始めた場合には「文字列とシンボルは同じもの」と誤解することがあるかもしれません。


5.4 Hash のキーに mutable な String オブジェクトを使っても大丈夫なの!?

大丈夫です。

Hash のキーに String オブジェクトを使用した場合、それをコピーした新たなオブジェクトへの参照を保持します。

このため、キーに使用した String オブジェクトを破壊的に変更しても、キーはその影響を受けません。

irb> str = "foo"

=> "foo"
irb> hash = {str => str}
=> {"foo" => "foo"} # この時点でキーは str と異なるオブジェクトを参照している

irb> str << "bar" # ここで str が参照するオブジェクトを破壊的に変更してみる
=> "foobar"
irb> hash
=> {"foo" => "foobar"} # 値は破壊的変更の影響を受けているが、キーは影響を受けていない
irb> hash["foo"]
=> "foobar" # 従って、"foobar" でなく "foo" で値を取得できる

ただし、Hash のキーに Array オブジェクトなどを使用した場合はこのようなコピー処置は行われません。

(そんなことをする人はあまりいないと思いますが・・・)


6. おわりに

以上、オブジェクト周りを中心に Ruby の基本について解説しました。

個人的に、Ruby は書くことが楽しいプログラミング言語であると感じます。

その理由の一つに「Ruby ではすべてがオブジェクト」という特徴が挙げられるのではないかと思っています。

本記事が少しでも Ruby 導入への手助けになれれば幸いです。

また、不明点やツッコミがあれば随時コメントいただけると嬉しいです。