遅延評価

[id:lethevert:20051124:p1]
先日、こんなことを書きましたが、結局、あれこれ考えた結果、やっぱりだめだという結論になりました。
何がどうダメだったかは、いずれ時間のあるときにゆっくり書くとして、このことから考えたことを書こうと思います。

遅延評価のメリット

HaskellConcurrent Cleanは、副作用に対する制限が厳しすぎて、遅延評価のメリットを享受しきれていないのではないかと思いました。
遅延評価の最大のメリットは、「宣言的に」コードが書けることだと理解していますが、副作用に対する態度が潔癖すぎるために、副作用が含まれるコードは手続き的に書くことが強制されてしまいます。
しかし、通常のプログラムは、入出力を伴うことが必須なため、

  1. 全てメモリに溜め込む
  2. 全て手続き的に記述する

のどちらかを選択する必要があります。
で、ある程度の規模のデータ量を処理するプログラムになると1つ目は非現実的になるので、2つ目を選択する必要が出てくるのですが、関数型言語で手続き的に書くのは何をやっているのか分からない!
むしろ、ある程度の規模になってくると、オブジェクト指向言語のほうが、宣言的な記述で遅延評価的なプログラミングを行うことが圧倒的に書きやすくなってきます。たとえば、昨日のwgetの例でいうと、

class Web {
  private WebPage webPage;
  private Web[] linkedWebs;

  Web (WebPage webPage) {
    this.webPage = webPage;
    this.linkedWebs = new Web[webPage.countLink()];
    for (w in linkedWebs) {
      w = null;
    }
  }

  WebPage getWebPage () {return webPage;}
  Web getLinkedWeb (int i) {
    if (linkedWebs[i] == null) {
      linkedWebs[i] = new Web (webPage.getLinkedWebPage(i));
    }
    return linkedWebs[i];
  }
}

と書くわけで、確かに、昨日のCleanの

::Web = Web WebPage [Web]
getWeb page
    = WebPage page (map getWeb pages)
where
    pages = getLinkedPages page

と比べれば、人間コンパイラなコードを書く必要がありますけれど、Cleanのコードは実際には動かないコードですから。

何が問題なのか?

これは、当初想像していたよりもずっと深いテーマではないかと思い始めてきました。
遅延評価そのものが有用であることは、オブジェクト指向プログラミングにおいても常套的なプログラミングテクニックとして利用されていることからも分かるわけで、宣言的に記述して遅延評価されるという機能は、非常に有用な機能と言えるのですが、それを本当に有用な機能とするのは、言うは易く行うは難い典型と思います。
たとえば、Haskellは、インタプリタがあるので、入出力は全てインタプリタにすれば、矛盾を考えずに済むかもしれません。それに類似して、emacsgimpのマクロとしてもよさそうです。よく考えてみると、これらは、全て一度メモリにためるというタイプのプログラムなんですね。
では、どういうプログラムが矛盾を感じるのか?
典型例はやはりwgetではないかと思います。これを例に、ポイントを考えてみると、

  1. あらかじめどのリソースが必要になるかが分からない
  2. リソースが外部にあり、アクセスにコストがかかる

という場合に、遅延評価が有用で、かつ、副作用のため遅延評価が利用できないという矛盾が起こります。
では、この矛盾を解消する方法はあるのか?ということについては、この次のテーマにしようと思います。