Chapter 14 Lazy Values

※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

第 14 章 遅延評価val (lazy value)

遅延評価val (lazy value) は、値の初期化を最初にアクセスされるまで遅延させる方法です。これは実行中に必要とならないかもしれない、計算コストが高い値を扱う場合に有用です。最初の例として、従業員のデータベースを考えましょう。各従業員ごとにマネージャーとチームが決まっています。

case class Employee(id: Int,
    name: String,
    managerId: Int) {
    val manager: Employee = Db.get(managerId)
    val team: List[Employee] = Db.team(id)
}

上記の Employee クラスは、直ちにそのフィールドをすべて初期化し、従業員テーブル全体をメモリにロードします。これは明らかに最善ではなく、フィールドを lazy にすることで簡単に改善できます。このようにして、データベースアクセスを、本当に必要になるまで、また、初めて必要になるまで遅らせます。

case class Employee(id: Int,
                    name: String,
                    managerId: Int) {
    lazy val manager: Employee = Db.get(managerId)
    lazy val team: List[Employee] = Db.team(id)
}

実際に何が起きているか、いつレコードがフェッチされるか表示するモックアップのデータベースを使って、見てみましょう。

object Db {
    val table = Map(1 -> (1, "Haruki Murakami", 1),
                     2 -> (2, "Milan Kundera", 1),
                     3 -> (3, "Jeffrey Eugenides", 1),
                     4 -> (4, "Mario Vargas Llosa", 1),
                     5 -> (5, "Julian Barnes", 2))

    def team(id: Int) = {
        for (rec <- table.values.toList; if rec._3 == id)
            yield recToEmployee(rec)
    }

    def get(id: Int) = recToEmployee(table(id))

    private def recToEmployee(rec: (Int, String, Int)) = {
        println("[db] fetching " + rec._1)
        Employee(rec._1, rec._2, rec._3)
    }
}

一人の従業員を取り出すプログラムを実行すると、確かにデータベースは遅延評価Val を参照するときのみアクセスされることが、出力によって確認できます。

ほかの 遅延評価val の使い方は、いくつかのモジュールからなるアプリケーションの初期化の順番を解決することです。遅延評価Val が導入される前は、同様のことを object 定義を使用することで実現していました。二つ目の例として、いくつかのモジュールからなるコンパイラを考えてみましょう。最初に、シンボルのためのクラスと2つの事前定義された関数を定義している、単純なシンボルテーブルを見てみます。

class Symbols(val compiler: Compiler) {
    import compiler.types._

    val Add = new Symbol("+", FunType(List(IntType, IntType), IntType))
    val Sub = new Symbol("-", FunType(List(IntType, IntType), IntType))

    class Symbol(name: String, tpe: Type) {
        override def toString = name + ": " + tpe
    }
}

symbols モジュールは、Compiler インスタンスでパラメータ化されています。Compiler インスタンスは、types モジュールなどのほかのサービスへのアクセスを提供します。この例では、事前定義された関数が2つ (加算と減算) だけあり、それらの定義は types モジュールに依存しています。

class Types(val compiler: Compiler) {
    import compiler.symtab._

    abstract class Type
    case class FunType(args: List[Type], res: Type) extends Type
    case class NamedType(sym: Symbol) extends Type
    case object IntType extends Type
}

2つのコンポーネントをつなぐため、コンパイラオブジェクトを作成して、2つのコンポーネントへ引数として渡します。

class Compiler {
    val symtab = new Symbols(this)
    val types = new Types(this)
}

残念ながら、この実直的なアプローチは実行時に失敗します。symtab モジュールが types モジュールを必要としているからです。一般的に、モジュール間の依存は複雑になりがちで、正しい順番で初期化するのは難しく、循環があるために不可能なことさえあります。簡単な対処は、そのようなフィールドを lazy にして、正しい順番は compiler に任せてしまうことです。

class Compiler {
    lazy val symtab = new Symbols(this)
    lazy val types = new Types(this)
}

これで、2つのモジュールは最初のアクセスで初期化され、compiler は期待通りに動くでしょう。

構文

lazy 修飾子は、具体的な値定義でのみ指定できます。値定義におけるすべての型付け規則が遅延評価valにも適用されますが、ひとつだけ制限が取り払われています。それは、再帰的なローカル値が許されることです。


  • すべての従業員テーブルをメモリにロードします (?訳微妙) => 「従業員テーブル全体を」の方がいいかもです。 -- pomu0325 (2009-12-29 19:32:07)
  • (?訳微妙。my は?) => 原文が&quotby&quotのtypo と思われます。 -- pomu0325 (2009-12-29 19:32:39)
    名前:
    コメント:
最終更新:2011年02月24日 09:04
ツールボックス

下から選んでください:

新しいページを作成する
ヘルプ / FAQ もご覧ください。