标签归档:lazy

scala里模拟javascript/python里的生成器的效果

上一篇关于yield的字面含义,基本有答案了。对于python和javascript不太熟悉,这两个语言里yield都是配合生成器而使用的。比如下面这段 javascript 的例子从mozilla的网站上看到的,稍加改造:

➜  cat test.js

function* foo() {
  var index = 0;

  while (index <= 2 ) {
    console.log("here" + index);
    yield index++;
  }
}

var it = foo();

console.log("generator ready");

console.log(it.next());
console.log(it.next());
console.log(it.next());

它的输出结果是

➜  node test.js
generator ready
here0
{ value: 0, done: false }
here1
{ value: 1, done: false }
here2
{ value: 2, done: false }

在Scala里yield并不等效于javascript/python里的效果,要等效的话,必须通过lazy, Stream等延迟计算特性来实现:

➜  cat Test.scala
object Test {
  def main(args: Array[String]) {

    lazy val c = for( i <- (0 to 2).toStream ) yield {
      println("here" + i);
      i+1
    }

    println("ready")

    for( n <- c) {
      println(n)
    }
  }
}   

运行结果:

➜  scala Test.scala
ready
here0
1
here1
2
here2
3       

lazy变量与双重检测锁(DCL)

scala里的lazy变量,背后的实现是个典型的双重检测锁(DCL)模式。比如:

class A{
    lazy val a=2
}

对a的访问,先通过状态标记变量(做一次位与操作)判断是否已经初始化过。通过scalac -Xprint:jvm来看对a的访问:

lazy private[this] var a: Int = _;

// 通过标记变量的与1做位与操作,判断是否已初始化
lazy def a(): Int = if (A.this.bitmap$0.&(1).$asInstanceOf[Byte]().==(0))
  A.this.a$lzycompute()
else
  A.this.a;

看看状态变量(需要用volatile修饰) 和延迟计算的逻辑:

// 定义一个可变的Byte类型的变量 bitmap$0
@volatile private[this] var bitmap$0: Byte = 0.$asInstanceOf[Byte]();

// 延迟计算的函数
private def a$lzycompute(): Int = {
  {
    // 线程安全
    A.this.synchronized({
      // 按位与操作,
      if (A.this.bitmap$0.&(1).$asInstanceOf[Byte]().==(0))
        {
          A.this.a = 2;
          // 改变 bitmap$0 的值,标记a已经赋值过了
          A.this.bitmap$0 = A.this.bitmap$0.|(1).$asInstanceOf[Byte]();
          ()
        };
      scala.runtime.BoxedUnit.UNIT
    });
    ()
  };
  A.this.a
};

为什么用 Byte 变量而非用Boolean,是当有多个lazy成员时,可以用同一个状态变量,按”位”标记,而不必用多个Boolean变量来标识各个lazy成员的状态。

比如有a,b,c 三个lazy成员,则 bitmap$0 第一位表示a,第二位表示b,第三位表示c,以此类推;判断时只要分别与1,2,4,8… 做位与操作就知道了。

不过Byte类型最大只有8位,一个类中如果超过8个lazy成员,编译器就把 bitmap$0 状态变量改用Int类型了。