Skip to content

scala tips : DynamicVariable

Taisuke Oe edited this page Apr 11, 2015 · 1 revision

##scala.util.DynamicVariableで動的スコープ変数をエミュレートする

さて、ここを読まれている読者の方は、静的スコープと動的スコープの違いについて知っている人も多いかと思いますが、知らない人のために、簡単な具体例で説明します。

function main() {
  var x = 200
  function caller() {
    var x = 100
    callee()
  }
  function callee() {
    println(x) //200なら静的スコープ、100なら動的スコープ
  }
}
main()

JavaScript風の擬似言語ですが、雰囲気は伝わるのではないかと思います。ここで、callee()関数の中で100が出力されれば、その言語では、変数のスコープは動的であり、200が出力されれば静的ということになります。 ぶっちゃけて言うと、変数が参照されたとき、関数のコールスタックを呼び出し先からたどって一番最初に見つかった変数定義を参照するのが動的スコープ、変数の参照箇所から、字面上で外側にある変数を参照するのが静的スコープ、ということになります(若干厳密さを欠いては居ますが)。

さて、現在使われている、Scalaを含むほとんどの実用言語(Java, C/C++, C#, Ruby,...)は変数のスコープに静的スコープを採用しています。これには色々な理由がありますが、それはとりあえず脇に置いておきます。本題は、変数が静的スコープな言語でどうやって動的スコープな変数を実現するかです。ここまで長々と前置きしてようやく本題です。で、結論から言うと、Scalaではscala.util.DynamicVariableというクラスによって、比較的容易に動的スコープ変数をエミュレートできます。

まずは具体例を見てみましょう。

import scala.util.DynamicVariable 

object UsingDynamicVariable extends App {
  val x = new DynamicVariable[Int](500)

  def f1() { 
    println("before f2():" + x.value) // 400
    x.withValue(300){
      f2()
    }
    println("after f2():" + x.value) // 400
  }

  def f2 () {
    println("before f3():" + x.value) // 300
    x.withValue(200){
      f3()
    }
    println("after f3():" + x.value) // 300
  }

  def f3() {
    println("in f3():" + x.value) // 200
  }

  def main() {
    println("before f1():" + x.value)
    x.withValue(400) { 
      f1() 
    }
    println("after f1():" + x.value)
  }
  main()
}

このコードをscalac(2.9.0以降)でコンパイルして実行すると、

before f1():500
before f2():400
before f3():300
in f3():200
after f3():300
after f2():400
after f1():500

と表示されるはずです。ポイントは、 x.withValue(100){ ... }というコードです。x.withValue(100) { ... }は、xの値として100を設定した状態でブロックを評価して、ブロックが評価し終わったら、xの値をwithValueが呼び出される前の状態に戻します。 このwithValueの連鎖によって、コールスタックのたどりをエミュレーションしているわけです。さて、長々と説明してきましたが、このDynamicVariableという代物、役に立つのでしょうか?これに関しては色々議論がありそうですが、使い方によっては面白い応用ができます。

たとえば、githubに置いてある

import scala.util.DynamicVariable
object JssonBuilder {
  private val status = new DynamicVariable[Map[String, Any]](null)
  class ArrowOperatorExtension(key: String) {
    //Warning: this could cause unexpected behavior
    def ->(value: Any): (String, Any) = {
      val result = (key, value)
      status.value = status.value + result
      result
    }
  }
  def %(children: => (String, Any)): Map[String, Any] = {
    status.withValue(Map[String, Any]()) {
      children
      status.value
    }
  }
  def $(elements: Any*): List[Any] = elements.toList
  implicit def string2ArrowOperatorExtension(key: String)
    : ArrowOperatorExtension = {
    new ArrowOperatorExtension(key)
  }
}
{//prevent to namespace tainted
  import JssonBuilder._
  val obj = %{
    "X" -> 1
    "Y" -> 2
    "Z" -> 4
    "nums" -> $(1, 2, 3, 4, 5,
       %{"hoge" -> 1; "foo" -> 2} //nesting is OK
    )
  }
  println(obj)
}

こんなコードのようにして、言語内DSLを構築するのに役立ったりします。