(2015年までの)odaillyjp blog

イベント参加記録とプログラミング系の雑記

Ruby でスコープを自作する

最近 ipad の防水ケースと無線ルータを購入して、風呂場に ipad を持ち込めるようになったので、お風呂に入りながら ipad電子書籍を読んでいます。ちょうど『作って学ぶプログラミング言語 RubyによるScheme処理系の実装』という書籍を読み終えたのですが、この書籍は素晴らしいです。僕は今までクロージャというものを間違って理解していたことが分かりました。せっかく勉強したのだから、ブログで「クロージャとは何なのか」を書こうとしたのですが、最初から書くのがあまりに大変でしたので、クロージャを説明する前に必要になるスコープについて、Ruby で自作する方法を書いてみます。

以下のコードを考える

まずは以下の Ruby のコードを見てください。

puts lambda { |x|
  lambda { |x|
    x
  }.call(2) + x
}.call(1)

このコードを実行すると何が出力されるでしょうか。もちろん、3 が出力されます。
Ruby のローカル変数は、その変数が宣言されたブロックの終わりまで有効ですので、3行目の変数 x は 2 になり、4行目の変数 x は 1 になり、2 + 1 で 3 が出力されます。

ブロックの中で宣言した変数がどこまで有効なのか、いわゆる「スコープ」と呼ばれているものが分かっていれば答えを導くことができますが、このスコープというのはどうやって実装されているのでしょうか。

スコープの実装

スコープは以下のようにして実装することができます。

  • 空の要素を入れたリストを作る。
  • 変数に値を代入するときは、リストの先頭の要素に変数の情報(名前と値の組み合わせ)を格納する。
  • 変数の値を取得するときは、リストの先頭から順番に要素を見ていき、最初にマッチした変数の値を取得する。
  • スコープを作るときに、リストの先頭に空の要素を加える。
  • スコープを抜けるときに、リストの先頭から要素を取り除く。

この実装を Ruby で書いてみます。

# environment.rb
class Environment
  # 空の要素を入れたリストを作る
  def initialize
    @env = [Hash.new]
  end

  # 変数に値を代入する
  def set_variable(variable, value)
    @env.first[variable] = value
  end

  # 変数の値を取得する
  def get_variable(variable)
    alist = @env.find { |alist| alist.key?(variable) }
    alist[variable] if alist
  end

  # スコープを作る
  def scope
    # スコープを作るときに、リストの先頭に空の要素を加える
    @env.unshift Hash.new

    yield if block_given?

    # スコープを抜けるときに、リストの先頭の要素を取り除く
    @env.shift
  end
end

さっそく動かしてみます。

require 'enviroment'

$_ENV = Environment.new

# 変数 x に 1 を代入する
$_ENV.set_variable(:x, 1)
# 変数 y に 2 を代入する
$_ENV.set_variable(:y, 2)

# 変数 x を出力する
p $_ENV.get_variable(:x)
# => 1

$_ENV.scope do
  # 変数 x に 3 を代入する
  $_ENV.set_variable(:x, 3)

  # 変数 x を出力する
  p $_ENV.get_variable(:x)
  # => 3

  # 変数 y を出力する
  p $_ENV.get_variable(:y)
  # => 2 
end

# 変数 x を出力する
p $_ENV.get_variable(:x)
# => 1

同じ名前の変数でも、スコープの外と中で取得される値が違うものになっていますね。スコープのようなものが出来上がりました。しかし、私が作った Enviroment クラスは副作用がある実装になっていますので、知らずのうちに環境を壊してしまう可能性があります。書籍『作って学ぶプログラミング言語 RubyによるScheme処理系の実装』では副作用がない実装について書かれていますので、気になる方はそちらを読んでみてください。