はじめに
こんにちは、現在アメリカの大学で語学を学びながらソフトウェアエンジニアになるために独学で勉強しているものです。
今回は、Rubyのスコープについて整理した内容を共有します。
スコープは少し苦手意識があって避けていたのですが、しっかり向き合い、記事にまとめてみました。
もし内容に誤りがあればご指摘いただけると幸いです。
スコープとは?
プログラム内の変数やメソッドには、それぞれアクセス可能な範囲が決まっています。
この範囲のことをスコープと呼び、変数の種類によって異なるスコープが適用されます。
この記事では、Rubyの代表的な変数のスコープについて、それぞれの用途やアクセス範囲を解説します。
ローカル変数(メソッド)
ローカル変数は、メソッドやブロック内で定義され、呼び出しできる範囲が限定されます。
他の部分からアクセスや変更ができないため、安全に使えるのが特徴です。
def greet
message = "Hello, World!" # メソッド内でローカル変数を定義
puts message #メソッド内なので問題なく出力できる
end
greet # => "Hello, World!"
puts message # エラー: `message`は未定義
message
変数をgreet
メソッドの中で定義しているので、メソッド内のみアクセス可能ですが、メソッドの外ではエラーが発生します。
では、変数を外側で定義した場合はどうなるでしょうか。
chat = "Good morning" #ローカル変数
def morning_greet
puts chat # ローカル変数をメソッド内で呼び出そうとするが、メソッドのスコープによりエラー発生
end
morning_greet # エラー: `chat`は未定義
puts chat # => "Good morning"
変数chat
を外で定義して、morning_greet
メソッド内で呼び出そうとするとエラーが発生します。
一方、メソッドの外側からputs chat
で呼び出すとGood morning
が出力されます。
これからわかる通り、メソッドの場合、外で定義された変数は内では使えず,内で定義された変数は外では使えません。
これがメソッドにおけるローカル変数の挙動です。
ローカル変数(ブロック) ~~追記~~
一方ブロックになってくると性質が変わり、外で定義された変数が内で使えます。(内で定義された変数を外で使えないのはメソッドと同じ)
どういうことでしょうか。コメントで頂いたコードの例をすこし変えて説明していきます。
x = 0
y = 100 if false # 決して実行されない
1.times do
x = 9 # この x は 1 行目で定義されたローカル変数
y = 10 # この y は 2 行目で定義されたローカル変数
end
puts x # => 9
puts y # => 10
1.times do
z = 6 # この z はブロック内で定義されたローカル変数
end
puts z # => NameError(上記の z のスコープ外だから)
ローカル変数x
とy
はブロック外で定義されています。
2行目は後置ifを使用しており、決して実行されない処理です。
また、z
はブロック内で定義されたローカル変数であるため、スコープ外で呼び出してもNameError
となってしまいます。
さっきのメソッドと違い、ブロック外で定義したx
とy
にアクセスできているということです。
しかし、決して実行されないローカル変数y
を変数と認識して処理をしています。
2 行目のy
への代入式は実行されません。つまり代入は行われません。
ですが、この式の存在によって y
が定義されたことになります。
別の言い方をすると,スクリプトの実行時ではなく,Ruby の処理系がスクリプトを読み込んで解釈した時点でローカル変数の定義が決定される,ということですね。
ということらしいです。
ローカル変数でもメソッドとブロックで違う挙動をすることを頭に入れておきましょう。
グローバル変数
グローバル変数は、プログラム全体でアクセス可能な変数で、$を変数名の前につけることで定義されます。
全てのクラスやメソッドから参照・変更できるため便利ですが、予期せぬ影響が出やすいためあまり使われないとされています。
$global_variable = "I'm accessible everywhere!"
def display_global
puts $global_variable
end
display_global # => "I'm accessible everywhere!"
puts $global_variable # => "I'm accessible everywhere!"
インスタンス変数のスコープ
インスタンス変数は、特定のインスタンスに対して有効なスコープを持ち、異なるインスタンス間で共有されません。
@
を変数名の前に付けることで定義され、外部から直接アクセスすることはできませんが、attr_reader
やattr_accessor
でゲッターやセッターを定義することで、外部から値を取得・設定することが可能です。
class User
def initialize(name)
@name = name
end
def show_name
puts @name
end
end
user1 = User.new("Alice")
user2 = User.new("Bob")
user1.show_name # => "Alice"
user2.show_name # => "Bob"
この例では、user1
とuser2
でそれぞれ異なるインスタンス変数@name
を持ち、異なる状態を保持しています。
クラス変数のスコープ
クラス変数は、クラス全体で共有されるスコープを持ち、@@
を変数名の前につけて定義します。
クラスとその全インスタンス間で情報を共有するために使用されますが、クラスの継承時には予期せぬ動作をすることがあるため注意が必要です。
class Product
@@count = 0
def initialize(name)
@name = name
@@count += 1
end
def self.total_count
@@count
end
end
p1 = Product.new("Laptop")
p2 = Product.new("Phone")
puts Product.total_count # => 2
上記の例では、@@count
はすべてのインスタンスで共有され、インスタンスが生成されるたびに増加します。
また、クラス変数に対してはattr_reader
やattr_accessor
は使用できないため、total_count
のようにクラスメソッドで参照する必要があります。
まとめ
上記のまとめです。
- ローカル変数(メソッド)
メソッドの外で定義された変数は内で使えず、内で定義された変数も外では使えない。 - ローカル変数(ブロック)
外で定義された変数が内で使えて、内で定義された変数は外では使えない。
外部に影響を与えないため、安全。 - グローバル変数: プログラム全体でアクセス可能。便利だが、予期しない影響が出やすい。基本的には使われない。
- インスタンス変数: 各インスタンスに固有で、異なるインスタンス間で共有されない。
attr_reader
などで外部からのアクセスを調整。 - クラス変数: クラス全体で共有され、全インスタンスで共通のデータを持つ場合に使用。継承時の動作には注意が必要。