やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

「Ruby プログラムの実行」を読む9(代入、定数、変数)

 やっとこの章が終わる。

成果物

情報源

代入

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

 []=だの属性代入だのってのは何だよ。事前に説明してくれよ。わかんねーよ。知らねーよ。知ってること前提で話すなよ。ググってみた。たぶん[]=は配列の添字を指定して代入する式のことっぽい。演算子のオーバーライドによって実装できるようだ。属性代入というのはsendメソッドを使った代入だと思われる。たぶん。

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

 ダウト。定数は一回しか代入できないだぁ?

 嘘つけ! 警告が出るだけで代入できちゃうだろが!

 公式ドキュメントに騙されないよう注意してください。

MAX = 1
MAX = 2
p MAX
warning: already initialized constant MAX
warning: previous definition of MAX was here
=> 2

 ちなみに定数のつくりかたはほぼ変数とおなじ。先頭1文字目を大文字にしたら定数になる。

多重代入

まだ

 あ、そう。でもキーワードは手に入った。ググってみると以下のような概念のことらしい。

a, b = 1, 2

 あーはいはい。Pythonではタプル型を分解して別の変数にひとつずつ代入するアレね。

 ほかにも細かい記法があるっぽい。

a, = 1, 2, 3
a #=> 1
_, a, = 1, 2, 3
a #=> 2
*, a = 1, 2, 3
a #=> 3
a, *, b = 1, 2, 3, 4
a #=>1
b #=>4

変数と定数

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

 え、いまさら?

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

 はい。

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

 その前にスコープの概念は説明しなくていいの?

ローカル変数

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

 スコープの概念だと思うけど。スコープって単語は出てこない。

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

 ここでやっとフレームの説明がきた。「フレームとは実行時に生成される構造である」と。ブロックの所持者らしい。以下のようなときに作られる。

  • プログラムテキストのトップレベル(ruby に渡したファイル名、-e、ロード)
  • メソッド実行
  • クラス/モジュール定義式
  • BEGIN および END 文

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

 メソッドで考えるとわかりやすいね。

def m
  local_var = 1
end

 クラスでもローカル変数になるの? 外側からはアクセスできないけども。あ、ローカル変数だからそういうものか。つまりクラス内ではどこでも使える変数とうことね。クラス内限定のグローバル変数みたいなものか。

 でもそれ、クラス変数と何が違うの? ああ、クラス変数は外部から参照できるけど、クラス内ローカル変数は外部から参照できないのか。と思ったけど、どっちも外部からアクセスできなかった。

class C
  local_var = 1
  @@class_var = 2
end
C::local_var    #=> undefined method `local_var' for C:Class (NoMethodError)
C.local_var     #=> undefined method `local_var' for C:Class (NoMethodError)
C.new.local_var #=> undefined method `local_var' for #<C:0x006f1668> (NoMethodError)
C::class_var    #=> undefined method `class_var' for C:Class (NoMethodError)
C.class_var     #=> undefined method `class_var' for C:Class (NoMethodError)
C.new.class_var #=> undefined method `class_var' for #<C:0x0042a938> (NoMethodError)

 ググったらさらに混乱した。

 ちょっとまった。クラスインスタンス変数? どうやら次の3つがあるらしい。

 つまり、initializeメソッド内で@...と書いたらインスタンス変数だけど、クラスブロック内に書いたらクラスインスタンス変数になるってことか。で、アクセスできるところも変わると。

 あーもーダメだ。複雑すぎる。頭パンクするわ。

 情報もまとまってないし。公式ドキュメントにクラスインスタンス変数なんて書いてなくね? はぁ。いいかげんにしてくれよ。

 もういいや。今はスルーしよう。

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

 ん? コンパイル? Rubyインタプリタなのに? あー、evalの文字列を解析することをコンパイルと呼んでいるのか。

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

 ローカル変数のネストってなんだよ。変数ってネストできるもんなの? ポインタのこと? (shadowing)って書いてあるな。

 シャドーイングって、Rust言語でみたぞ。Rustのシャドーイングについて参照。

同名の変数が再宣言されたとき、前者を隠す(再宣言以降は前者は参照不可となり後者が参照される)。

 というか、Rubyって変数宣言しないよね?

 もういいや。日本語が意味不明だからコードで動作確認しよう。

a = '1'
def m
  a = 2
  p a
end
m   #=> 2
p a #=> 1
def m
  a = 1
  def n
    a = 2
    p "n:#{a}"
  end
  n #=> "n:2"
  p "m:#{a}"
end
m #=> "m:1"

 想定通り。よくわからんが、問題ないだろう。たぶん。

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

 ふーん? selfうんたらはよくわからん。

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

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

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

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

 たのむから動作するコードで書いてくれ。

 たぶんきっと以下のようなことだろう。

['A','B','C'].each_with_index do |v,i|
  p "#{i}:#{v}"
end

 上記のdoendが実行されるときは、まず最初に以下が実行される。

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

 each_with_indexの実装を知らないからyieldされているかどうかは知らんけども。

インスタンス変数

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

class C
  def initialize
    @name = 'A'
  end
  def get_name; @name; end
end
C.new.get_name #=> 'A'
C.new.@name #=> syntax error, unexpected instance variable (SyntaxError)
C.@name #=> syntax error, unexpected instance variable (SyntaxError)

 特定のインスタンスだけが持っているもの。それはいい。未定義のインスタンス変数を参照するとnilが返るってマジ? 例外じゃないの?

class C; end
C.new.not_define_var
undefined method `not_define_var' for #<C:0x017cc688> (NoMethodError)

 あ、そうか。これだとメソッド参照になるのか。クラス内からしか参照できないんだっけ。

class C
  def m; @name; end
end
C.new.m
=> nil

 うわ、マジだ。nilが返ってくる。

 えー、例外NameErrorにしてくれよ。これじゃタイポしただけでバグるじゃんか。ひどいな。

remove_instance_variable

 上記だけ書かれてもね。ググったら以下メソッドが出てきた。

オブジェクトからインスタンス変数 name を取り除き、そのインスタンス変数に設定されていた値を返します。

 インスタンス変数を削除するってこと?

クラス変数

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

 インスタンス変数とくらべてクラスからも参照できるようになった。ただし定義内からしか参照できないのは相変わらず。こっちは未定義参照するとNameErrorらしい。インスタンス変数はnilを返したのに、なぜ挙動が違うんだ。混乱する。

class C
  @@cls_var = 1
  def im; @@cls_var; end
  def self.cm; @@cls_var; end
  def self.cm_ndv; @@not_define_var; end
end
C.@@cls_var #=> syntax error, unexpected class variable (SyntaxError)
C.cm #=> 1
C.new.im #=> 1
C.cm_ndv #=> uninitialized class variable @@not_define_var in C (NameError)

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

 継承止めってなに? 相変わらず説明がまったくない。

 ググったらそれらしい機能はないっぽい。なぜかデザインパターンが出てきた。

 継承よりも集約したほうが疎結合になってよいという。

 ああ、これはデザインパターンも学習せねばならないな。後回しだけど。

クラスインスタンス変数

 ドキュメントにはない。でも存在するっぽい。

class C
  @cls_var = 1
  def im; @cls_var; end
  def self.cm; @cls_var; end
end
C.@cls_var #=> syntax error, unexpected instance variable (SyntaxError)
C.cm #=> 1
C.new.im #=> nil
class D < C; end
D.cm #=> nil
  • インスタンスメソッドからアクセスするとnilが返る
  • 継承した子孫クラスから参照するとnilが返る

 ようするに定義したクラスオブジェクトからしか参照できない、ということかな?

グローバル変数

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

$global = 1
def m
  p $global #=> 1
  $global = 2
  p $global #=> 2
end
m

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

 は?

定数

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

 え、定数の所属先はクラスとモジュールだけなの? フレームってやつじゃないの?

 え、メソッドで定数つくれないの? やってみたらできなかったよ。マジか。

def m
  MAX = 1
end
dynamic constant assignment (SyntaxError)

 以下は使わないだろう。

 ようするに定数を定義する場所は次のうちどれかってことか。

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

 なんだよ「本来の仕様ではありません」って。見苦しい言い訳はよせ。現状が仕様のソフトウェア業界だろ。そんなにいうなら例外にしてくれや。

MAX = 1
MAX = 2
p MAX
warning: already initialized constant MAX
warning: previous definition of MAX was here
=> 2

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

定数名のみの場合 (例: Const) 定数が所属するクラス、そのサブクラス、ネストしたクラスのフレームに書かれたコードから参照可能

フルパス指定の場合 (例: Mod::Cls::Const) あらゆる場所から参照可能

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

 ふーん。

所感

 定数がキモチワルイ。もっとちゃんとしてくれよ。ぜんぜん定まってないやん。

 クラスインスタンス変数ってなんだよ。公式ドキュメントにはないけれど実際には存在する。しかも記述がきわめて紛らわしい。罠としかいいようがない存在。

対象環境