Rubyプログラミングの基礎知識
この記事は、書籍『実践Ruby on Rails 4 現場のプロから学ぶ本格Webプログラミング』の内容を、Think IT向けに特別にオンラインで公開しているものです。詳しくは記事末尾の書籍紹介欄をご覧ください。
本書の読者としてはプログラミング言語Rubyの概念や文法をひと通り学習した方を想定していますが、初級者が間違いやすいポイントや『改訂新版 基礎Ruby on Rails』(インプレスジャパン刊)で書き残した項目について本章で解説します。
インスタンス変数
「インスタンス変数」。単純そうで意外に奥が深い概念です。もう一度おさらいしておきましょう。
インスタンス変数とは
インスタンス変数は、特定のオブジェクトが排他的に所有する変数です。そのオブジェクト自身だけがその値を参照できます。インスタンス変数という名前は、クラス変数との対比で用いられています。クラス変数は、あるクラスのすべてのインスタンスが共有する変数です。インスタンス変数は、それぞれのインスタンスごとに固有の値を持ちます。
Rubyにおいてクラスはオブジェクトの一種ですので、クラスもインスタンス変数を持ちます。その値はクラス自身だけが参照できます。そのクラスのインスタンスからは参照できません。
次のプログラムをご覧ください。
~/chapter03/robot01.rb
class Robot @name = "ROBOT" def name @name end end r = Robot.new p r.name
2行目にあるインスタンス変数@nameはRobotクラス自体と結びついています。他方、5行目のインスタンス変数@nameは、Robotクラスの各インスタンスによって所有されます。同じ名前ですが、まったく別の変数です。この点は非常に重要です。
9行目のローカル変数rはRobotクラスのインスタンスです。rからは5行目の@nameは見えますが、2行目の@nameは見えないのです。したがって、このプログラムの実行結果は次のようになります。
$ ruby robot01.rb nil
もし2行目の意図が、Robotクラスのインスタンスにデフォルトの名前を与えることであるとすれば、本当は次のように書くべきでした。
~/chapter03/robot02.rb
class Robot def initialize @name = "ROBOT" end def name @name end end r = Robot.new p r.name
これならば、プログラムの結果として"ROBOT"という文字列が表示されます。
インスタンス変数はコンストラクタ(initializeメソッド)またはインスタンスメソッドの中で定義し、使用します。クラス定義の直下で定義されたインスタンス変数はそのクラス自体によって所有され、そのクラスの各インスタンスからは参照できません。
属性
インスタンス変数はそれを所有しているオブジェクト以外からは参照できません。値(オブジェクト)を代入することもできません。外部からのアクセスを許すには、次のように参照用と代入用のメソッドを用意する必要があります。
~/chapter03/robot03.rb
class Robot def name @name end def name=(name) @name = name end end
この種のメソッド定義は頻繁に行われるので、もっと簡便なやり方が用意されており、上の例は次のように書き換えられます。
~/chapter03/robot04.rb
class Robot attr_reader :name attr_writer :name end
attr_readerは引数に指定したシンボルに対応するインスタンス変数を参照するメソッドを定義するクラスメソッドです。同様にattr_writerは代入用のメソッドを定義してくれます。参照用と代入用のメソッドを一度に定義したい場合は、attr_accessorが使用できます。
~/chapter03/robot05.rb
class Robot attr_accessor :name end
こうして定義されたnameのことを属性と呼びます。しかし、その実体はnameおよびname=というメソッドに過ぎません。
末尾が等号(=)のメソッド名
Rubyのメソッド名で使用できる文字は英数字とアンダースコア(_)のみですが、特別ルールとして、メソッド名の末尾に疑問符(?)、感嘆符(!)、そして等号(=)が使用できます。
robot09.rbの6~8行ではname=というメソッドが定義されています。名前が等号で終わるメソッドは、さらに特別ルールでrobot.name = "Alice"のように呼び出せることになっています。これはrobot.name=("Alice")と同じ意味です。robot.name = "Alice"という式を「name属性に代入」と表現する場合もありますが、正確に言えばname=メソッドを呼び出しているのです。
なお、7行目の等号の右にあるnameは6行目の丸括弧の中にあるnameと同じものです。つまり、name=メソッドへの引数です。2~4行で定義されているnameメソッドと解釈されてもよさそうですが、そうはなりません。Rubyインタープリタは7行目でnameに出会ったとき、それがローカル変数または引数として定義されていないかどうかを調べます。定義されていない場合だけメソッドとして呼び出します。
遅延初期化
前項のプログラムでインスタンス変数@nameに初期値として"ROBOT"という文字列を与えたい場合、次のように書きます。
~/chapter03/robot06.rb
class Robot attr_accessor :name def initialize @name = "ROBOT" end end
しかし、次のように書き換えることも可能です。
~/chapter03/robot07.rb
class Robot attr_writer :name def name @name ||= "ROBOT" end end
Robotオブジェクトを作るときに@nameを初期化するのではなく、nameメソッドが初めて使用されたときに@nameを初期化しています。その際、すでにname=メソッドによって値がセットされていれば、デフォルト値による初期化は行われません。このような実装方法を遅延初期化(lazy initialization)と呼びます。
遅延初期化の利点は2つあります。1つは、nameメソッドが使用されない場合、@nameを初期化しなくてもよいということです。ここでは"ROBOT"という文字列を作るだけなのでほとんど効果はありませんが、デフォルト値に指定するオブジェクトの生成が重い処理である場合(たとえば、インターネットやデータベースから値を取得する場合)には、リソースの節約になります。
もう1つの利点は、インスタンス変数を初期化するコードと使用するコードが近接する(同一メソッド内にある)ということです。上記の例では1個しかインスタンス変数がないのでメリットを感じにくいですが、インスタンス変数の個数が増えてくるとコードの可読性を上げる効果があります。
なお、nameメソッドの中身は次のコードの省略形です。
@name = @name || "ROBOT"
@nameが偽値、すなわちnilかfalseの場合に@nameに"ROBOT"を代入します。したがって、真偽値を扱うインスタンス変数の場合には、この書き方ではうまくいきません。たとえば、
def activated? @activated ||= true end
のようにactivated?メソッドを定義したとします。しかし、@activatedにfalseを代入してもこのメソッドはtrueを返します。正しくは次のように書きます。
def activated? @activated = true if @activated.nil? end
この記事の著者
黒田 努
東京大学教養学部卒。同大学院総合文化研究科博士課程満期退学。ギリシャ近現代史専攻。専門調査員として、在ギリシャ日本国大使館に3年間勤務。中学生の頃に出会ったコンピュータの誘惑に負け、IT業界に転身。株式会社ザッパラス技術部長、株式会社イオレ取締役を経て、技術コンサルティングとIT教育を事業の主軸とする株式会社オイアクスを設立。現在、同社代表取締役社長。また、2011年末にRuby on Rails によるウェブサービス開発専業の株式会社ルビキタスを知人と共同で設立し同社代表に就任(オイアクス社長と兼任)。
連載「実践Ruby on Rails 4 現場のプロから学ぶ本格Webプログラミング」の記事一覧
関連記事
- 第2回 基本機能の実装にチャレンジ!
- 第2回 Active Recordの使い方
- 第1回 カメラで写した写真を分離ストレージに保存し、写真の一覧を表示する
- 画面に並んだ写真が指の動きに反応して回転するLeap Motionプログラムを作る
- マウス・カーソルで指定した画像の一部を拡大表示するLeap Motionプログラム
- Leap Motionで画像のトリミングと保存を行うためのサンプルプログラムを作る
- タッチパネルでドラッグ&ドロップを使う汎用的なサンプル
- 第4回 OpenRoadsでロボットアプリケーションを作る
- 第7回 撮影した写真を分離ストレージとPicturesHUBに保存する
- 第2回 「どんなシステムを作るのか」をJavadocで表現する