人気ブログランキングへ

2009年02月24日

Yet Another Haskell Tutorial (和訳): 3 Haskell言語の基礎

第3章

Haskell言語の基礎


この章では、Haskellの基本的な概念を示す。対話的環境に慣れてもらい、簡単なプログラムをコンパイルする方法を示すだけでなく、Haskellの基本的な文法を紹介する。CやJavaのような言語に慣れているとしたら、おそらくHaskellはまったく異質な言語だろう。
しかし、この言語の詳細を述べる前に、Haskellの一般的な特性のいくつかをはっきりさせておく必要がある。もっとも重要なのは、Haskellがlazyな言語だということだ。つまり、Haskellは計算結果が使われる時点で計算を実行せざるをえなくなるまで計算を実行しない。
これは、たとえば、データ構造全体を使用しなければ、無限に大きなデータ構造を定義できることを意味する。たとえば、擬似的な命令的コードを用いても、以下のようなことをすれば各位置に数値1を含む無限のリストを生成することができる。

List makeList()
{
List current = new List();
current.value = 1;
current.next = makeList();
return current;
}

このコードを見れば、何をしようとしているかわかるだろう。新しいリストを作り、値を1に設定し、再帰的に自分自身を呼び出してリストの残りの部分を作る。もちろん、実際にこのコードを作って呼び出せば、プログラムはいつまでも終了しない。なぜならmakeListは無限に自分自身を呼び出し続けるからだ。
それは、この命令的な言語を (lazyと反対の) strictな言語と仮定しているからだ。strictな言語は "call-by-value"、lazyな言語は "call-by-name" と呼ばれることが多い。上記の擬似コードでは、5行目でmakeListを「実行」するとそのを得ようとする。これは無限ループとなる。
Haskellの同等のコードは以下のようになる。

makeList = 1 : makeList

このプログラムは以下のように読める。makeListという何かを定義しようとしている。これが等号の左側の処理だ。等号の右側でmakeListを定義している。Haskellでは、コロン演算子はリストの生成に使われる (これについては、すぐ後で述べる)。等号の右側は、makeListの値はmakeListの値の先頭に要素1が加えられたものであると言っている。
しかし、Haskellはlazyなので (つまり、call-by-nameなので)、この時点ではmakeListが実際に何なのかを評価しようとしない。将来makeListの2番目の要素が必要になったときにmakeListを見る必要があることを、単に記憶しておくのだ。
さて、makeListをファイルに書き込もうとしたり、画面上に表示しようとしたり、あるいは要素の合計を計算しようとしたりすると処理は終了しない。というのも、無限長のリストを評価しなければならないからだ。しかし、リストの有限な一部分 (たとえば、最初の10要素など) を使うだけならリストが無限長であることは問題とならない。最初の10要素だけを使うなら、最初の10要素だけが計算される。これがlazyということである。
次に重要なのは、Haskellが大文字と小文字を区別する (case-sensitive) ということだ。そのような言語は多い。だが、実はHaskellでは大文字と小文字で意味が異なる。Haskellは (たとえば、1, 2, 3,...といった数値)、文字列 ("abc", "hello",...)、文字 ('a', 'b', ' ',...)、 (値が属するカテゴリ) を区別する。
そのこと自体は特殊ではない。ほとんどの言語は何らかの型というシステムを持っている。Haskellが特殊なのは、関数と値は小文字で始まらなければならないし、型は大文字で始まらなければならない教訓は、ほかの点では正しいプログラムなのにコンパイルに失敗する場合は、関数にFooといった大文字から始まる名前をつけていないか確かめることだ。
Haskellは関数型言語なので、副作用が起きることを避けている。副作用は本質的に関数の実行の過程で起こるものであり、その関数から生成される出力には関係しないものである。
たとえば、CやJavaのような言語では、"グローバル" 変数を関数内から変更できる。このグローバル変数に変更を加えても関数が生成する出力には関係しないので、これは副作用である。さらに、実世界の状態を変更することも副作用とみなされる。画面に何かを表示したり、ファイルを読み込んだり、などはすべて副作用の処理である。
副作用を持たない関数を純関数 (pure function) という。ある関数が純関数かどうかを調べる簡単な方法は、自分自身に次のような簡単な質問をすることだ。「この関数は、同じ引数を与えたらつねに同じ結果を返すだろうか?」
これはつまり、コードを (CやJavaのような) 命令的言語で書くことに慣れているなら、別の考え方をしなければならないだろうということにほかならない。もっとも重要なのは、値xがあったら、xをレジスターや記憶場所といった性格のものとして考えてはならないことだ。xは、私の名前が「Hal」なのとまったく同じように、単なる名前なのだ。私の名前のところに別の人物を格納しようなどと勝手に決めることはできないのと同じで、xのところに別の値を格納しようなどと勝手に決めることはできない。つまり、以下のCコードのようなコードはHaskellでは正しくない (同等のコードも存在しない)。

int x = 5;
x = x + 1;

x = x + 1のような呼び出しは、実行前にxに何が入っていたとしてもそれを破壊して新しい値に置き換えるため、破壊的更新と呼ぶ。Haskellに破壊的更新は存在しない。
破壊的更新 (やそのような副作用を引き起こす処理) を許可しないため、Haskellのコードはとても理解しやすい。つまり、関数fを定義し、プログラムの先頭である引数aでその関数を呼び出し、プログラムの最後で同じ引数afを再度呼び出したら、同じ結果が得られる。なぜなら、aは変更されないし、faのみに依存するからだ (たとえば、グローバルカウンタがカウントアップされたりしていない)。この性質を参照透明性 (referential transparency) といい、基本的には、2つの関数fgが同じ引数に対して同じ値を生成するなら、fgに置き換えることができる (逆もできる) ことを主張している。

■注■ 参照透明性の正確な定義は人によって異なる。私は上記の定義が最もよいと思っている。誰でも解釈は同じである。表現のしかたが異なるだけだ。


前ページ「2.4 エディタ
次ページ「3.1 計算


5 Basic Input/Output
6 Modules
7 Advanced Features
8 Advanced Types
9 Monads
10 Advanced Techniques
A Brief Complexity Theory
B Recursion and Induction
C Solutions To Exercises


これは、Haskell (ハスケル) のチュートリアル "Yet Another Haskell Tutorial" を日本語に翻訳したものです。
オリジナルのドキュメントは、
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
などから入手できます。
日本語訳に関するご指摘は、コメントとしてお寄せください。

posted by K/I at 18:30 | 東京 🌁 | Comment(0) | TrackBack(0) | Yet Another Haskell Tutorial | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

※ブログオーナーが承認したコメントのみ表示されます。

この記事へのトラックバック
×

この広告は90日以上新しい記事の投稿がないブログに表示されております。