JQ Blog

ブロック①

スコープ

スコープとは

スコープは範囲、またはエリアなどで定義できる。つまり、プログラミングでのスコープはどこまで参照できるかを決める範囲を指してスコープと呼ぶ。

スコープの変更

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
v1 = 1

class MyClass
  v2 = 2
  local_variables # => [:v2]

  def my_method
    v3 = 3
    local_variables
  end

  local_variables # => [:v2]
end

obj = MyClass.new
obj.my_method # => [:v3]
obj.my_method # => [:v3]
local_variables # => [:v1, :obj]

このスコープの移動を追っていると、
まず、v1が定義されたトップレベルのスコープだ。次に、MyClassが定義されたスコープだ。
Rubyの「内部スコープ」から「外部スコープ」を参照する「入れ子構造」が存在しないため、スコープはきちんと区別されている。新しいスコープに入ると、束縛も新しい束縛と交換される。なので、my_methodが呼び出されるとさらに新しいスコープがオープンされて終わったらスコープは削除され、トップレベルのスコープに戻る。そこでまたmy_methodを呼び出すと新しいスコープをオープンして、新しいv3を定義する。(ここで定義されたv3は先のv3とは関係ない)

スコープゲート

プログラムがスコープを切り替えて、新しいスコープをオープンする場所は3つある。

  • クラス定義(class)
  • モジュール定義(module)
  • メソッド呼び出し(def)

スコープのフラット化

1
2
3
4
5
6
7
8
9
my_var = "Success"

class MyClass
  # my_varを表示したい。。

  def my_method
    # my_varを表示したい!
  end
end

このコードでクラスとメソッドでトップレベルのロカール変数のmy_varを使いたい場合、どうすればいいのか。今までの説明だったらスコープがそれぞれ違くて使えないけど。 さっき言った通り、スコープゲートは3つがある。
つまり、スコープゲート以外にはスコープ関係なくトップレベルのローカル変数を使えるということになるのだ。
classdefの代わりにClass.newdefine_methodを使えばクロージャでmy_varを取得できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
my_var = "Success"

MyClass = Class.new do
  puts "#{my_var} はクラスの中で表示できる!"

  define_method :my_method do
    puts "#{my_var} はメドッドの中で表示できる!"
  end
end

MyClass.new.my_method

# =>
Success はクラスの中で表示できる!
Success はメドッドの中で表示できる!

こういうふうに書くと、無事出力される。

気になる点

そうしたらdefine_methodにブロックを渡したらどうなるだろう。

1
2
3
4
5
6
define_method :my_method do
  yield
end

my_method { puts "hello!" }
# => LocalJumpError: no block given (yield)

エラーになる。

1
2
3
4
5
6
7
def my_method
  yield
end

my_method { puts "hello!" }
# =>
hello!

defでメソッドを定義する時は問題なく動く。
define_methodでブロックが使える方法とは何があるのかな。

1
2
3
4
5
my_lambda = lambda{ puts "hello!" }

define_method :my_method, my_lambda
# =>
hello!
1
2
3
4
5
my_proc = proc{ puts "hello!" }

define_method :my_method, my_proc
# =>
hello!

lambdaprocを使うとdefine_methodにブロックを渡せる。

まとめ

  • どこまで参照できるかを決める範囲をスコープと呼ぶ
  • スコープはきちんと区別されている。新しいスコープに入ると、束縛も新しい束縛と交換される
  • スコープゲートは3つがある
    • クラス定義(class)
    • モジュール定義(module)
    • メソッド呼び出し(def)
  • classdefの代わりにClass.newdefine_methodを使えばスコープのフラット化ができる
  • define_methodにもブロックが渡せる