LoginSignup
2
0

More than 1 year has passed since last update.

ローカル変数/インスタンス変数/定数/クラス変数

Posted at

ローカル変数

ローカル変数は大きく2つのメリットがあります。

  • 宣言したメソッドやブロック内で使用できる
    まず、1つ目のメリットですが、ローカル変数を呼び出せる範囲はグローバル変数などに比べると狭いです。これにより、コードの流れを追ったり、修正したりすることが比較的容易になります。

  • パフォーマンスが向上する
    また、ローカル変数は値をキャッシュするので、同じ処理を繰り返す場合には、変数をローカル変数に代入しておくことで、パフォーマンスの向上が期待できます。

必要な間接層が最低限で済むのがローカル変数です。
Rubyでローカル変数を参照するときは、変数を格納しているメモリをすぐに参照できます。これは Ruby の仮想マシンがローカル変数の位置を把握しているからです。しかも、ローカル変数のほとんどは仮想マシンのスタックに積まれます。スタックに積まれた値はCPUキャッシュにも乗りやすくなります。

ローカル変数を使う理由は、コードをより明確かつ効率的にするためです。ローカル変数を使うことで、一時的なデータを格納して再利用することができます。これにより、コードを簡素化し、可読性を高めることができます。また、ローカル変数は、グローバルスコープで定義された変数よりもメモリ使用量を削減できるため、パフォーマンスの向上にもつながります。

以下はローカル変数を活用した例です。
例えば、ARGV[0].to_fというコードですが、これはdoブロックの中に書かれているため、ブロックがループする度に、呼び出されてしまいます。

large_array.reject! do |value| 
  value / 2.0 >= ARGV[0].to_f
end

そこで、上のコードを以下のようにローカル変数を用いて書き換えると、
doブロックの中で、何度もmaxを使用する必要がなくなり、パフォーマンスが向上する可能性があります。

max = ARGV[0].to_f * 2 
large_array.reject! do |value|
  value >= max 
end

ローカル変数で注意した方が良いこと

冪等性を担保する

ローカル変数を使用する際は、変数に保存する式が冪等である必要があります。
つまり、同じ式を何度実行しても、その結果が変わらないようにする必要があります。

これにより、同じ式を何度も実行する代わりに、一度だけ実行して結果を変数に保存することで、パフォーマンスを向上させることができます。

しかし、式に副作用がある場合は、同じ式を何度実行しても、その結果が変わってしまう可能性があるため、ローカル変数を使った最適化ができません。このため、式に副作用がある場合は、ローカル変数を使った最適化を行うことができません。

単純な例ですが、このコードは、array のすべての要素を加算して count に代入することを意図しています。ただし、このコードは冪等性がありません。同じ array を複数回処理することはできず、ローカル変数の最適化に使用することはできません。

count = 0
array = [1, 2, 3, 4, 5]
array.each do |n|
  count += n
end
puts count

スコープゲートを把握する

Rubyのローカル変数は「Rubyがコードをパースしてローカル変数を最初に見つけたところから、変数が定義されているスコープが終わるまでの間」が有効範囲です。

スコープゲートとは
スコープゲートは、Rubyのスコープを定義するキーワード(def, class, module、ブロックなど)のことを指します。スコープゲートが開始すると、その中で定義された変数やメソッドは、外側のスコープからはアクセスできなくなります。そして、スコープゲートを抜けると、以前のスコープが復元され、以前に定義された変数やメソッドに再びアクセスできるようになります。

要はローカル変数は、def/class/moduleなどの中で定義された場合、その中でしか使用できないようになります。そして、そこを抜けると、その前に定義された変数やメソッドを使用できるようになります。

以下はそれを表すコード例です。

defined_(a) #nil
a=1
defined?(a) # 'local-variable' 

#ここでmoduleを定義
module M
  defined?(a) # nil
  a=2
  defined?(a) # 'local-variable' 

  #ここでclassを定義
  class C
    defined?(a) # nil
    a=3
    defined?(a) # 'local-variable' 

    #ここでdefを定義
    def m
      defined?(a) # nil
      a=4
      defined?(a) # 'local-variable'
    end
    #defを抜けた直後
    a#3 
  end
  #classを抜けた直後
  a#2 
end
#moduleを抜けた直後
a#1

スコープゲートを超える

しかーし!!
「この変数を他でも使いたいな、、、」と思ったときに、スコープゲートを超える方法がrubyにはあるわけです。以下のようにスコープゲートなしで定義すれば良いです。

スコープゲートあり スコープゲートなし
def define_method
class Class.new
module Module.new

例えば、以下のように記述した場合、通常のクラスでは、outerという変数を呼び出すことができません。
なぜなら、ローカル変数がスコープの範囲外にあるからです。

outer = "外から呼び出されました。"
Class MyClass

  puts "私は#{outer}"

  define_method :my_method do
    "私は#{outer}"
  end
end

しかし、以下のようにClass.newと記述することにより、outerというローカル変数を呼び出すことができます。

outer = "外から呼び出されました。"
MyClass = Class.new do

  puts "私は#{outer}"

  define_method :my_method do
    "私は#{outer}"
  end
end

インスタンス変数

インスタンス変数を追加することで、実行性能を向上できます。
ローカル変数による最適化と同じ原則がインスタンス変数全般にも適用できます。
=>複数回呼び出される傾向のあるメソッドで、呼び出し結果が冪等であれば、結果をインスタンス変数に保存することで実行性能を向上できる。 

以下の例では、Calculator クラスに add メソッドが定義されています。
add メソッドは、引数で渡された2つの数値を加算し、インスタンス変数 @result に結果を保存します。最初に add メソッドが呼び出された場合には、@result が nil なので、結果をそのまま代入します。2回目以降の呼び出しでは、既存の結果に新たな計算結果を加算します。

このように、複数回呼び出されるメソッドの場合には、計算結果をインスタンス変数に保存することで、実行性能を向上できます

class Calculator
  def initialize
    @result = nil
  end
  
  def add(a, b)
    if @result.nil?
      @result = a + b
    else
      @result += a + b
    end
  end
  
  def result
    @result
  end
end

calc = Calculator.new
calc.add(1, 2)
calc.add(3, 4)
puts calc.result # 10

測値オブジェクトはインスタンス変数を定義できない

Rubyの中で、true、false、nil、整数(Integer)、小数(Float)、シンボル(Symbol)はいずれもリテラル(literal)で表現可能な、特別なオブジェクトとして扱われます。

これらのオブジェクトには、それぞれ1つのオブジェクトが割り当てられており、他のどの場所でも同じオブジェクトが使われます。これらのオブジェクトは、オブジェクトIDを調べると、その値が同じであることがわかります。

そのため、true、false、nil、Integer、Float、Symbolといった即値オブジェクトは、実際にはオブジェクトであるにもかかわらず、インスタンス変数を持たないため、インスタンス変数を定義することができません。ただし、これらのオブジェクトに対しても、クラスにインスタンス変数を定義することは可能です。

例えば、以下のコードはエラーになります。

true.instance_variable_set(:@var, "value")

定数

rubyで定数はCONSTANTのように書きます。
定数でややこしいのはスコープです。ローカル変数ともインスタンス変数とも異なります。

クラス定数を探索する場合、まずはそのクラスやモジュールを探索します。そこで、見つからなかった場合は、スーパークラスを探索します。段階的に上へ上へ探しに行くという流れですね。

例えば、classAとclassBがあったとして、BがAを継承したとしましょう。
この場合、classBで定数を呼び出した場合、Xは4、Zは5です。
一方で、WとYについては、classBに定義されていないので、classAの定義結果となります。

class A 
  W=0 
  X=1 
  Y=2 
  Z=3
end

class B < A 
  X=4
  Z=5 
end

しかし、これがネストすると非常にややこしい。。。
まだ理解しきれていないので、またの機会にまとめてみたいです。

定数の可視性

クラスインスタンス変数と違って、定数は外部から参照できます。
この定数を外部から参照できないようにするには、以下のようにprivate_constant :CONSTANT
のように書きます。

class A
  X = 2
  private_constant :X 
end

クラス変数

クラス変数は、そのクラスの全てのインスタンスで共有される変数です。
@@hogeのように書きます。

  • クラスとそのインスタンスがスコープになる
  • クラス変数は何度でも値を変更できる
  • クラスメソッド、インスタンスメソッド、クラス定義式内でアクセス可能

正直あまり使わないです。
なぜなら、スコープが広すぎるからです。

例えば、以下のようなクラスでクラス変数が定義されていたとして、
Bでクラス変数を変更した場合、そのクラスだけでなく、他のクラスの変数も変更されてしまいます。
これではあまりにも扱いづらいです。

class A 
  @@a = 1
  
  class << self
    @@a
  end
  
  def b
    b = @@a
  end 
end

class B < A
  @@a
end
2
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0