この文章は About Haskell(A Short Introduction to Haskell)永田章人が勝手に翻訳したものです。著作権などに問題がある場合は即刻撤去しますので、永田に御連絡下さい。
ちなみに文章がおかしいところは英語に自信がないところです。 表現が間違っていたり、誤植など直した方がよいところがあれば是非御指導ください。

About Haskell

Haskellはプログラミング言語の一つです。 特に 多相型により型付けされ, 遅延評価される, 純粋な関数型 という点で他の言語とは大きく異なります。 この言語は 関数型言語の基礎となる記号論理学に従事したHaskell Brooks Curryにちなんで名付けられました。 Haskellはラムダ計算に基づいており、それがロゴとしてラムダが使われている理由です。

なぜHaskellをつかうのか?

巨大なソフトウェアシステムを書くことは困難でコストがかかります。 それらのシステムを維持していくことはさらに困難でコストがかかります。 Haskellのような関数型言語はその作業を容易かつ安価にすることができます。

純粋な関数型言語であるHaskellは以下を提供します。

Haskellは多くの種類のアプリケーションに適した汎用性の高い言語です。 特に何度も書き直したりメンテナンスを行なったりするプログラムに適しています。

ソフトウェアの生産では、多くの時間が費やされるのは仕様の決定、デザインメンテナンスであり、プログラミング自体ではありません。 関数型言語は仕様を書くのに最適です。その仕様は同時に(テストとデバッグもできる)実行可能なプログラムでもあり、 最終成果物としてのプログラムの最初のプロトタイプとすることができます。

同様に、関数型プログラムではコードが短く簡潔になり、 厳格な副作用の制御により予期のできない相互作用を排除することが可能なため、 メンテナンスがしやすくなります。

関数型言語とは?

C, Java, Pascal, Adaといった言語は 命令型(imperative)言語です。 これらは逐次実行されるコマンドの列により構成されているという意味で"命令型"と呼んでいます。 Haskellは関数型言語です。関数型プログラムはただ一つの式からなり、 それが評価されることによりプログラムは実行されます。 表計算ソフトを使ったことがある人は関数型プログラミングを 経験済みだと言えるでしょう。 表計算ソフトでは各セルの値を、他のセルの値を基に決定します。 ここで注目されるのは を計算するのかであり、 どう計算されるかではありません。 例えば

この表計算ソフトにおける計算の順序が指定されないということから、 代入という概念はそれほど役にたたないという面白いことが言えます。 とりわけ、もし代入がいつ起こるか正確にわからないのであれば、それを有効利用することはできません! このことは、 基本的に代入文を慎重に並べていくことにより計算を構成していく C言語や、メソッドの呼び出し順が重要となるJavaといったような慣習的な言語とはかなり対称的です。

この レベルの低い"どう" ではなく レベルの高い"何" に焦点を当てているのが、 関数型言語が他と最も異なる特徴と言えます。

他のよくしられた関数型言語に近い言語として、 標準的なデータベースクエリー言語である SQLがあります。 SQLのクエリーは投射、選択、結合などを含めた式になります。 クエリーには どの関係が計算されるかが記述され、どのように計算するかは 記述されません。 もちろんクエリーはどのような順序で評価されても構いません。 SQLの実装の多くは、(考え得る中で)最も最適な式の評価順序を見つけることで クエリーの最適化を行ないます。

関数型言語の何がいいのか?

表計算ソフトやSQLは両方とも特別な場合にのみ使用される言語です。 関数型言語は同じアイデアを汎用プログラミングの領域に採り入れます。 関数型プログラムがどのようなものであるかわかってもらうために、 下に示すようなクイックソートのプログラムを見てもらいます。 この2つのプログラムは数値の列を"クイックソート"と呼ばれる計算方法で昇順に並べ変えるものです。 最初のプログラムは Haskellで書かれており、 2つめのプログラムはCで書かれています。

Haskellで書かれたクイックソート

qsort []     = []
qsort (x:xs) = qsort elts_lt_x ++ [x] ++ qsort elts_greq_x
                 where
                   elts_lt_x   = [y | y <- xs, y < x]
                   elts_greq_x = [y | y <- xs, y >= x]

Cで書かれたクイックソート

qsort( a, lo, hi ) int a[], hi, lo;
{
  int h, l, p, t;

  if (lo < hi) {
    l = lo;
    h = hi;
    p = a[hi];

    do {
      while ((l < h) && (a[l] <= p)) 
          l = l+1;
      while ((h > l) && (a[h] >= p))
          h = h-1;
      if (l < h) {
          t = a[l];
          a[l] = a[h];
          a[h] = t;
      }
    } while (l < h);

    t = a[l];
    a[l] = a[hi];
    a[hi] = t;

    qsort( a, lo, l-1 );
    qsort( a, l+1, hi );
  }
}
それではHaskellや関数型プログラミングの何がいいのか考えてみましょう。 関数型プログラミングのより詳細な事例については、
Why Functional Programming Matters by John Hughes, The Computer Journal, Vol. 32, No. 2, 1989, pp. 98 - 107. Also in: David A. Turner (ed.): Research Topics in Functional Programming, Addison-Wesley, 1990, pp. 17 - 42. (山下伸夫さんによる邦訳 : なぜ関数プログラミングは重要か)
で見つけることができます。上の論文に影響されたあまりフォーマルでないエッセイとして
Why Does Haskell Matter by Sebastian Sylvan
があります。

1. 簡潔さ

関数型プログラムは命令型の対応するプログラムに比べて、かなり簡潔になる傾向にあります。クイックソートはかなり顕著な例ですが、一般的に関数型プログラムは(2倍から10倍) 短いプログラムになります。

2. 理解のしやすさ

関数型プログラムは多くの場合理解が容易です。 Haskellの知識も、クイックソートの知識が全く無くとも プログラムを理解することができるでしょう。 Cのプログラムではそうはいきません。Cでは理解するのに長い時間かかった上、 ちょっとした間違いをしてしまい、正しくないプログラムができあがるのが落ちでしょう。 Haskellのクイックソートを詳しく説明します。
qsort []     = []
qsort (x:xs) = qsort elts_lt_x ++ [x] ++ qsort elts_greq_x
		 where
		   elts_lt_x   = [y | y <- xs, y < x]
		   elts_greq_x = [y | y <- xs, y >= x]
最初の行はこう読むことができます: "空のリスト([]と書かれている)をソートした結果は空のリスト"。 同様に2行目はこう読むことができます: " 最初の要素がxで、 残りの要素がxsであるようなリストを ソートするためには、 xsのうちxより小さい全ての要素 (elts_lt_xとする)と、 xsのうちxと等しいかxより大きい全ての要素 (elts_greq_xとする)と をそれぞれソートし、それぞれの結果にxを 真ん中に挟み込み結合(++)しろ。 elts_lt_xの定義はすぐ下で与えられています : リストxsから引き出された要素yのうち、 xより小さい全てのyのリスト"。 elts_greq_xの定義も同様です。 この構文は、"|"を "such that"、 "<-"を"取り出す(drawn from)"と読むことで、 標準的な数学の集合の表記を 連想できるようになっています。 空ではないリストをソートするように要求されたとき、qsortelts_lt_xelts_greq_xをソートするために自分自信を呼び出します。 これらのリストは元のqsortに与えられたリストよりも小さいのでOKです。 そのようにして、この分けてソートするというプロセスにより、対象となるリストは空のリストまで短くなります。 空のリストのソートはqsortの最初の行で与えられた、 ほぼ自明の方法でソートされます。

3. コアダンプがない

Haskellを始めとする多くの関数型言語は強く型付けされ、 プログラマがしがちな多くの種類のバグをコンパイル時に取り除くことができます。 特に強く型付けされることはコアダンプがない!ということを意味します。 これは単純に整数をポインタとして扱うことや、 nullポインタを辿るといったことがないためです。

4. コードの再利用

もちろん強い型付けはAdaやPascalのような多くの命令型言語でも利用可能です。 しかし、Haskellの型システムは、例えばPascalのそれに比べ、 自由度が高くなっています。それは多相型を取り扱うことができるからです。 例えば図1で与えられたクイックソートの プログラムは整数リストのみをソートするのではなく、 浮動小数点数のリストや文字のリスト、リストのリストなど 、<(less-than)と >(greater-than)演算子で比較出来る要素を 持つどんなリストでもソートすることが出来るでしょう。 それとは対象的に、Cのバージョンでは整数の配列のみに限られてしまいます。

多相型は再利用性を高めるのです。

5. 強い糊

非厳格な関数型言語には、答を得るのに必要な分だけプログラムを評価する、 というもう一つの強力な特徴があります。 このことは遅延評価(lazy evaluation)と呼ばれています。 この特徴は Unixのパイプに 似ているかもしれません。 例えば Unixのコマンドの
grep printf Foo.c | wc
Foo.c というファイルの中のprintfという文字列を含む行の数を数えます。 "grep printf Foo.c"というコマンドが"printf"を含む全ての行を抜き出してくる一方で、 "wc"コマンドがそれらの数を数えます。 "|"と書かれているのはパイプで、 最初のコマンドの出力を受けとり、2つ目のプログラムへ渡す役目をします。 2つのコマンドは同時に実行され、最初のコマンドの 出力は幾分の遅れはあるにしても、その直後に使用されることになります。 この方法では大きな中間ファイルを生成する必要はありません。 wcgrepから出力される行に"依存している" と考えることができます。

もし、2つ目のコマンドが最初のコマンドの出力のうちの一部しか使用しないのであれば、最初のコマンドは 最後まで次以降する必要はないかもしれません。例えば

grep printf Foo.c | head 5
は "printf"が含まれている行のうちの最初の5行だけを表示します。 grepを実行が中断されるかもしれないということを考慮にいれた プログラムに書き直す必要はありません。

非厳格な言語はこのような種類の要求駆動型の評価方法を提供します。 データ構造は答を導くのに十分となる分だけ評価され、 結局評価されないような部分式も含んでいるかもしれません。 Unixのコマンドの例でわかるように、 このことは既存のプログラムを組み合わせるための強力な"糊"を提供しています。 このことは命令型で書く場合よりも、 プログラムを再利用性の高い小さいなプログラムに分割できることを表しています。 遅延評価により、よりモジュール性の高いプログラムを書くことが可能となります。

6. 強力な抽象化

一般に、関数型言語は抽象化をカプセル化するための強力で新しい方法を持っています。 抽象化により内部の動作を隠蔽したオブジェクトを定義することが可能となります。 例えば、C言語の手続きはひとつの抽象化ということができます。 抽象化はモジュール性が高くメンテナンスのしやすいプログラムを構築する、 まさにそのとなります。従って、新しい言語に対して "抽象化のためのどういうメカニズムが提供されているか?" ということがよく問われます。

関数型言語における強力な抽象化のメカニズムに高階関数があります。 Haskellでは関数はファーストクラスです。 : 関数は他の関数へ自由に渡すことが出来るし、 関数の返り値として返すこともでき、データ構造の中に格納したりできます。 高階関数を適切に使うことにより多くのプログラムの構造やモジュール性を向上させることができます。

7. 組み込みのメモリ管理

多くの最近のプログラムではヒープ領域のメモリの動的確保を必要とします。 Cではこれは mallocを呼び出すことにより行なわれ、 そのあとその領域を初期化するコードが続きます。 その領域が必要なくなった時に 自由に使用できるメモリのプールへと戻すのはプログラマの責任であり、 これが悪名高い"dangling-pointer"エラーの元となります。 さらに、malloc はかなりの豪華な関数であるため、 プログラマーが mallocにより一つの大きな塊を確保して、 その中から"手動で" 確保することもよくあります。

全ての関数型言語はプログラマをこの重荷から解放させてくれます。メモリ確保と初期化は暗黙のうちに行なわれ、確保された領域はガーベジコレクターにより自動的に回収されます。 この記憶領域の確保とガーベジコレクションの技術は現在では適度に成熟しており、 小さいコストで実行できます。

Cの方がよい場合

もちろん関数型言語が全てにおいて勝っているわけではありません。 Cのクイックソートは Hoareによりあみだされた配列を置き換える ことでソートを行うという極めて巧妙なテクニックを用いているため、 余計な記憶領域を使う必要がありません。  結果として、少ないメモリ使用で高速に実行されます。  それとは対照的にHaskellのプログラムは裏で多くの余計なメモリの確保を行っており、 Cのプログラムよりも実行が遅くなります。

要するに、Cのクイックソートは読みやすさと引き換えに、 記憶領域のコストを減らすためのかなり巧妙な記憶領域管理を行っています。

パフォーマンスが最も重視されるようなアプリケーションや、 低レベルのアルゴリズムにより細部をチューニングすることが目的の場合は、 計算がどう実行されるかをより綿密に制御するコードを提供できるため、 Cのような命令型言語の方がHaskellを選ぶよりも賢明でしょう。

関数型 vs 命令型

しかし、何よりもパフォーマンスが求められるようなプログラムというのはそれほど多くありません。 何といっても、大昔の内部ループを残し、 我々は皆アセンブラでプログラムを書くことをやめました。  よりサポートの高いプログラミングモデル(任意の名前付き数、決まった個数のレジスタの代わりとなるローカル変数など)を採用することによる利益は、 実行時のコストを押えることよりもはるかに重要です。

同様に、仮想メモリページングシステムは喜んで受け入れられています。無限の仮想アドレススペースなどのよりサポートの高いプログラミングモデルを得ることができています。 今日、明示的なメモリの重ね付けは終わりました。

関数型言語は高いレベルのプログラミングモデルへ向けて大きなステップを踏もうとしています。 プログラムはよりデザイン、記述、保守がしやすくなりますが、 マシンの詳細な制御はできなくなります。 このことはほとんどのプログラムにとって許容範囲内です。

Haskellとは何か?

Haskell は近代的かつ標準的で非厳格な純粋な関数型プログラミング言語です。 多相型、遅延評価、高階関数など、上記の特徴は全て備えています。 また、オーバーロード体系的な構成やモジュールシステムをサポートした革新的な型システムも持っています。

表現力のある構文、従来の整数や浮動小数点数、 真偽値型に加え任意精度の整数や有理数 豊富な組み込みデータ型を備えており、 数値計算から抽象的なアプリケーションまで幅広くサポートできることに、 重点をおいてデザインされています。

多くの 実装が利用可能で、全てフリーとなっています。 初心者の方には 軽量で可搬性の高い Haskellインタプリタである Hugs をお勧めします。

誰か関数型プログラミングを使っている人はいますか?

関数型プログラミング言語は多くのアプリケーションで使用されています。例として などがあります。
その他の例についてはこのサイトのHaskell in practiceを参照してください。

その他のよくある質問

関数型プログラミングを学ぶのは難しいですか?
確かに関数型プログラミングは見方を変えることを要求しており、 それを難しいと感じるプログラマもいます。 しかし、ErlangのEricssonによるプログラマのトレーニングの実験では、 プログラマが 一朝一夕でできると考えずに、真剣にトレーニングを行なえば、 多くの場合は移行は容易いとしています。
関数型プログラムは遅いのではないですか?
かつてはそうでしたが、現在ではコンパイラが追いついて来ました。 パフォーマンス大きく依存するアプリケーションを除けば、 全てのHaskellプログラムは十分な速さで実行することができます。
CまたはC++で書かれた大規模アプリケーションが既にあるのですが、 システムの全体を書き直さずに関数型言語の恩恵を受けることができますか?
Haskellは 既存のアプリケーションにいろいろな方法で巧みに統合されて来ました。 HaskellDirectは IDL (Interface Description Language)ベースのツールで、 これによりHaskellプログラムを他のソフトウェアコンポネントと同時に使うことができます。 C/C++への低レベルインタフェースはGreen Cardで作成することができます。 これにより HaskellとCの強力な統合が可能です。 これらのツールを使って多くの多言語から構築された多くのシステムが、成功を納めています。
Haskellがサポートしているライブラリにはどういうものがありますか?
多くのHaskell用ソフトウェアライブラリが開発されました。 list of Haskell libraries に利用可能なライブラリの全リストがあります。
その他のHaskell用のソフトウェアツールにはどんなものがありますか?
Glasgow Haskellではプロファイラが利用できます。 これによりあなたのプログラムの中のどの部分が、 実行時間のメモリスペースのほとんどの割合を消費しているかがわかります。 Chalmers Haskellはスペースプロファイリングツールと、 プログラムを並列に動かした場合の実験ができるquasi-paralellシミュレータが利用できます。 Hugsでも同様のツールが利用可能です。
サポートや支援を受けることは可能ですか?
残念ながら、とりあえず現時点ではできません。 Haskell の実装はどれも研究グループにより作られたものであり、 これらの実装と企業が契約を結ぶことで、 市場でのチャンスが生まれるのではないか思います。 私たちはこのようなことを喜んでする人と共に働きたいと切に願っています!
どうしたらHaskellが学べますか?
より多くの例や説明は Gentle Introduction to Haskell (山下伸夫さんによる邦訳 : やさしいHaskell入門)を参照してください。 Haskellを利用するための書籍も数多く存在します。 bookshelfを参照してください。

この文章はSimon Peyton Jonesの論文に基づいています。(それをさらに日本語訳したものです。)


haskell.org | intro | report | papers | teaching | compilers | libraries | projects | future haskell | mailing list | links | :-)
最終更新日: 12 December 2001(日本語版は2003年9月24日)

永田のメモ: 非厳格(Non-strict)というのは関数の引数が遅延評価されることを指すようです。