Ruby プログラムの実行

Ruby プログラム

Ruby プログラムの実行は文の連なりの評価です。なんらかの形であたえられたプログラムテキストをコンパイルし、BEGIN 文があればそれを評価し、トップレベルの式の連なりを評価し、END ブロックがあれば最後にそれを評価して終了します (終了処理の詳細については 終了処理 を参照のこと)。

if

if 文は、まず条件式を評価し、その値が真ならば対応する本体を評価します。偽ならば elsif 節の条件式を順番に評価し、その値が始めて真になった節の本体を評価します。それらがすべて偽なら else 節の本体を評価します。

文全体の値は最後に実行した本体の値です。ただし評価された本体に式がなかった場合、あるいはすべての条件式が偽でかつ else 節もなかった場合は nil です。

until

if 修飾子

unless 修飾子

while

until

while 修飾子

until 修飾子

for

begin 〜 end

クラス定義式

クラスを定義します。評価は(コンパイル時ではなく)実行時に行われます。

書式

class ClassName [< スーパークラス式]
  式
end

クラス定義式は評価されるとまずクラスを生成しようとします。スーパークラス式が指定されていたらそれを評価し、その値を上位クラスとする Class クラスのインスタンスを生成します。式が省略されていたら Object を上位クラスとします。

一方、もし同名のクラスがすでにある場合はそれを使います。そのときスーパークラス式が指定されており、その結果と得たクラスのスーパークラスが (equal? において) 違う場合は例外 TypeError が発生します。

クラスを得たら次にそれを定数「ClassName」に代入します。これによってクラス名が決定されます。このとき同名の定数に Class のインスタンスでないものが代入されている場合は例外 TypeError が発生します。

最後に新しいフレームを生成し、トップレベルブロックの self および class に定義を行おうとするクラスを設定して、そのフレーム上で定義式中の式を評価します。クラス定義式は最後に評価した式の結果を返します。最後に評価した式が値を返さない場合は nil を返します。

つまり Ruby では何度も「クラス定義の追加」をすることが可能です。

モジュール定義式

モジュールを定義します。評価は(コンパイル時ではなく)実行時に行われます。

書式

module ModuleName
  本体
end

モジュール定義式は評価されるとまず新しい無名のモジュールを生成します。ただしすでに ModuleName と名付けられたモジュールがある場合はそれをかわりに使います。このような場合は「モジュール定義の追加」をすることになります。

モジュールを得たら次にそれを定数 ModuleName に代入します。この定数がモジュールの名前になります。このとき同名の定数にモジュール以外が代入されていた場合は例外 TypeError が発生します。

最後に、新しいフレームを生成し、そのトップレベルブロックの self および class にモジュール ModuleName を設定し、そのフレーム上で定義式中の式を評価します。モジュール定義式は最後に評価した式の結果を返します。最後に評価した式が値を返さない場合は nil を返します。

特異クラス定義式

オブジェクトの特異クラスを定義します。評価は(コンパイル時ではなく)実行時に行われます。

書式

class << EXPR
  本体
end

まず特異クラスを定義しようとするオブジェクトの式 EXPR を評価します。続いてそのオブジェクトの特異クラスを(まだ生成されていなければ)生成します。最後に、新しいフレームを生成し、トップレベルブロックの self および class に生成した特異クラスを設定し、そのフレーム上で定義式中の式を評価します。

特異クラス定義式は、最後に評価した式の結果を返します。評価する式がひとつもなければ nil になります。

ただし Fixnum Symbol のインスタンスおよび true false nil には特異クラスは定義できません。

メソッド定義式

メソッドを定義します。評価は(コンパイル時ではなく)実行時に行われます。

書式

def method_name(arg, argwithdefault=expr, *restarg, &block)
  本体
end

評価すると、実行中のブロックの class にメソッド本体を当該の名前で定義します。もしすでにその class 自体に同名のメソッドが定義されている場合は、古いメソッドを捨てて新しいメソッドの内容によって定義しなおします。

メソッド定義式は、メソッド名を Symbol にしたオブジェクトを返します。

特異メソッド定義式

オブジェクトの特異クラスにメソッドを定義します。評価は(コンパイル時ではなく)実行時に行われます。

書式

def expr.method_name(arg, argwithdefault=expr, *restarg, &block)
  本体
end

まず最初に式 expr を評価します。続いてその値のオブジェクトの特異クラスを (まだ作成されていなければ) 作成します。最後に、その特異クラスにメソッド method_name を定義します。

特異メソッド定義式は、メソッド名を Symbol にしたオブジェクトを返します。

ただし Fixnum Symbol のインスタンスおよび true false nil には特異メソッドは定義できません。

BEGIN

コンパイル時に登録される (評価は実行の最初)

END

コンパイル時に登録される (評価は実行の最後)

メソッド

メソッドの呼び出し

まずレシーバ式を評価してレシーバとなるオブジェクトを得ます。レシーバ式が省略された場合は呼び出しを行っているブロックのself がレシーバです。

続いて引数式を左から右の順番で評価し、レシーバに対してメソッドの検索を行います。検索が失敗したら例外 NameError を発生、成功したらメソッドを実行します。

またメソッドを実行する際にはブロックを与えることが可能です。メソッドに対してブロックを与えると、そのメソッドの実行中にyield が実行されたときにはじめてブロックが評価されます。yield されなかったときはブロックは単に無視され、実行されません。

メソッドにブロックを与える場合、そのブロックはメソッドを呼び出す側のブロックの self と class を継承します。Module#module_eval/class_eval、 BasicObject#instance_eval の三つだけが例外で、以下のように変更されます。

Module#module_eval, Module#class_eval

self、class ともそのレシーバ

BasicObject#instance_eval

self がそのレシーバ、class がそのレシーバの特異クラス

eval

eval の第二引数に Proc オブジェクトまたは Binding オブジェクトを与えたときは、その生成時のブロックのうえで文字列を評価します。

メソッドの実行

メソッドの実行はフレームのうえにひとつだけブロックがある状態で開始します。このブロックを以下、仮にトップレベルブロックと呼んでおきます。トップレベルブロックの self はレシーバで、class は定義されていません。

まず、省略不可能な引数が存在するなら、与えられた値をトップレベルブロックのローカル変数に代入します。

省略可能な引数が存在し、実際に省略されていたら、トップレベルブロック上でデフォルト式を評価し、その値をトップレベルブロックのローカル変数に代入します。実際には省略されなかったら、与えられた値を同じくローカル変数に代入します。

*args の形のパラメータ指定があるなら、残りの引数すべてを配列としてローカル変数に代入します。

さらに、ブロック引数 blockvar が存在するならば、メソッドに与えられたブロックを Proc オブジェクト化してトップレベルブロック上のローカル変数 blockvar に代入します。ブロックが与えられていないなら nil を代入します。

続いて本体が評価され、メソッドレベルの rescue 節または else 節が評価され、最後に ensure 節が評価されます。

メソッド全体の値は return に渡した値です。 return が呼び出されなかった場合は、本体/rescue/else の中で最後に評価した式の値です。その三つともすべてがカラだった場合は nil になります。

ブロック付きメソッド呼び出し

メソッドに対してブロックが与えられていたらそのメソッドをブロック付きメソッドと呼びます。ブロック付きメソッドからは yield によって与えられたブロックに実行を移すことができます。

ブロック引数をとることができます。

break … ブロックがスタックフレーム上にあるなら、そのフレームのブロックの直後にジャンプします。ブロック付きメソッドを break して終了したらその値は nil です。スタックフレーム上にないなら例外 LocalJumpError を発生します。

next ブロックの終わりまでジャンプ

retry 複雑だ…

eval, instance_eval, module_eval

これなんだっけ

代入

代入とは、変数・定数のいずれかにオブジェクトを記憶させることを言います。 []= や属性代入のメソッド呼び出しも文法上は代入のように見えますが、それはここで定義する代入ではありません。

変数には、何回でも、どんなオブジェクトでも代入することができます。定数にも同様にあらゆるオブジェクトを代入することができますが、ただ一回しか代入できません。つまりいったんオブジェクトを代入したらあとから記憶するオブジェクトを変更することはできません。これは記憶したオブジェクトそれ自身が変化することを禁止するのではないことに注意してください。

多重代入

まだ

変数と定数

変数および定数はオブジェクトをひとつだけ記憶しておくことができます。この、オブジェクトを記憶させる操作を「変数(定数)への代入」と言います。

変数および定数を評価すると、それが記憶しているオブジェクトを返します。この操作を「変数(定数)の参照」と言います。

以下、種類別に変数/定数の代入と参照の挙動を述べます。

ローカル変数

ローカル変数はただひとつのブロックに所属します。ブロックとはコードのある範囲に対応する実行時の構造で、ネストが可能です。具体的にはブロック付きメソッド呼び出しや eval 系メソッドの実行にともなって生成されます。ローカル変数は、所属するブロックおよびそのブロックの上にネストされたブロックの中からのみ代入・参照できます。

またさらにブロックは特定の「フレーム」のうえに積まれ、そこに所属します。別フレームのうえにあるローカル変数を参照することはできません。フレームとは以下の文の実行開始時に生成される実行時の構造です。

フレームが生成されると自動的にブロックもひとつ積まれますので、これらの文の本体でもすぐにローカル変数を使うことができます。

ローカル変数の定義は、コンパイル時にそのフレーム中で定義されていないローカル変数への代入をプログラムテキスト中に書くことによって行います。所属するブロックは代入が書かれている一番外側のブロックです。このことからもわかるように、ローカル変数の定義はコンパイル時にすべて完了します (eval 系メソッドは実行中にコンパイルをしていることに注意してください)。定義された変数の初期値は nil です。

ローカル変数の定義および参照にあたっては、外側のブロックから順番に変数を探します。この結果としてローカル変数のネスト (shadowing) はできません。ただし上下関係にない複数のブロックに別々のローカル変数が存在することはありえます。

また未定義の(つまりコード中に書かれていない)ローカル変数を参照すると、 Ruby は次にそれを self への(引数のない)メソッド呼び出しに解釈しようとします。メソッドの探索にも失敗したら例外 NameError を発生します。

呼び出しブロックの実行にあたっては、ブロックが引数をとることができますが、これは実行しようとするブロック上での多重代入とみなされます。たとえば以下のコードのブロック実行開始時には、

some_iterator do |a,b|
  ....
end

次のような操作がまず実行されます。

a, b = <some_iterator から yield された値>

インスタンス変数

インスタンス変数はひとつのオブジェクトに所属し、そのオブジェクトを self とするブロックだけから代入、参照できます。定義は代入によって兼ね、未定義のインスタンス変数を参照すると nil を返します。

remove_instance_variable

クラス変数

クラス変数はひとつのクラスとそのサブクラス、およびそのインスタンスに所属し、それらオブジェクトを self とするブロックだけから代入、参照できます。定義は最初の代入によって行います。未定義のクラス変数を参照すると例外 NameError が発生します。

クラス変数の継承と「継承止め」

グローバル変数

グローバル変数は全ての場所から代入、参照できます。定義は最初の代入によって行い、未定義のグローバル変数を参照すると nil を返します。

トレースできること(?)

定数

定数はクラス/モジュールに所属します。代入はメソッド以外のすべてで可能で、最初の代入が定義を兼ねます。定数が所属するクラスは代入が行われたブロックの class です。また非常に特殊な例外としてメソッド Module#const_set によっても定義が可能です。さらに Module#remove_const を使うことで定義の取り消しが可能です。

すでに定義されている定数の再定義および代入はできません。実際には警告のみで代入が可能ですが、これは一時的な避難措置であり本来の仕様ではありません。従ってこれに依存したコードはできるだけ書かないようにすべきです。

参照可能な範囲は記法によって分かれます。

定数名のみの場合 (例: Const)

定数が所属するクラス、そのサブクラス、ネストしたクラスのフレームに書かれたコードから参照可能

フルパス指定の場合 (例: Mod::Cls::Const)

あらゆる場所から参照可能

また ::Const のように :: を前置した記法では Object::Const のみが参照可能です。