Haskell について

このページでは、関数型プログラミング言語 Haskell を紹介します。

Haskell とは

Haskell (ハスケル)は、1990 年に発表された関数型言語です。 当時乱立していた関数型言語を統一しようというモチベーションから、複数の計算機科学者の手によって生み出されました。 Haskell という名前は、アメリカの数学者 Haskell Curry (1900-1982) の名に由来します。

Haskell は次のようなキーワードで表される特徴をもつ言語です。

これらのキーワードについて順番に説明していきたいと思います。

純粋関数型

関数型プログラミングになじみのない方向けに、まずは関数型言語の説明から。

関数型言語 (functional language) とは、 「関数」によってプログラムを構成 するプログラミングスタイルを基本とする言語のことです。 Haskell のほかに、LISP や ML が伝統的な関数型言語として有名です。

ここでいう 関数 (function) とは、いわゆる数学の関数と同じく、同じ引数に対して同じ値を返す プログラム部品のことをいいます。 例えば、C の標準ライブラリから例を出すと、正弦関数 sin は関数ですが、擬似乱数を返す rand は(C では関数と呼んでも)ここでいう関数ではありません。

関数と関数もどき

関数によってプログラムを構成する関数型言語では、for ループや while ループのような便利(?)な制御構造は使いません。 その代わり、繰り返し処理を関数だけで実現するために、関数型言語では再帰関数がよく使われます。

また、関数がプログラムの主役となる関数型言語では、関数を整数や文字などのデータと同じレベルで扱います。 つまり、関数リテラルが存在し、関数を別の関数の引数や返り値に使ったりすることが簡単にできます。 たとえば、Haskell なら、リスト [1, 2, 3, 4, 5] の要素を 2 乗する処理が、関数式を使って次のように簡単に書けます(2 つのハイフン -- から右側はコメントです)。

map (\x -> x * x) [1, 2, 3, 4, 5]  -- 値: [1, 4, 9, 16, 25]
変数に格納したり、関数の引数や返り値に使ったりできるデータのことを、第一級オブジェクト (first-class object) と呼びます。 関数型言語では、関数は第一級オブジェクトです。

関数型言語のなかでも特に 純粋関数型言語 (purely functional language) と呼ばれる言語は、同じ式はどのタイミングで評価しても同じ値になる という特徴をもつ言語です。 たとえば、f x という式 (C 言語風に書くと f(x))の値があるタイミングで 5 だったら、他のタイミングで f x を評価しても必ず 5 になることが保証されます。 これまでの説明だけだと「関数型言語はみんなそうなんじゃないの?」と思われると思いますが、実は LISP や ML のような言語だと、(実用性などの観点から)この性質を破るようなプログラムを書くことが許されています。

「同じ式はどのタイミングで評価しても同じ値になる」という性質は、参照等価性 (referential transparency) といいます。 この性質は、プログラムの解析を容易にし、品質のよいプログラムを構築するのに役立ちます。

モナド

先ほど Haskell は純粋関数型言語だという話をしましたが、現実のプログラムは(特に入出力周りで)副作用とは切っても切れない関係にあります。 純粋性を保ちながら副作用とうまく付き合うために、Haskell は モナド (monad) という概念を採用しています。

純粋関数型言語で副作用を実現する方法には、モナド以外にもいろいろな手法が知られています。 初期の Haskell は、モナドではなく遅延リストという仕組みで副作用を実現していました。 Haskell よりも古い純粋関数型言語である Clean では、一意型 (uniqueness type) という概念で同様のことを実現しています。

モナドは数学の圏論 (category theory) という分野から持ってきた概念です。 ただ、Haskell を理解するのに圏論の知識は必ずしも必要ないので、そこはご安心ください。

Haskell ではモナドのための強力な構文糖が提供されており、これがモナドの存在を Haskell においてより特別にしています。 次のプログラムに示す do 記法と呼ばれる構文糖がそれです。

main = do
    putStr "あなたの名前: "
    name <- getLine
    let greeting = "こんにちは、" ++ name ++ "さん。"
    putStrLn greeting

純粋関数型言語でありながら、いかにも手続き型言語であるかのような馴染みのあるスタイルでプログラムを書くことができます。

強力な型システム

Haskell は強い静的型付けの型システムを採用しています。 また、強力な型推論機構を持っています。

append x y = x ++ y

このプログラムは、2つのリストを連結する関数 append を定義した Haskell プログラムです。 コード上にはどこにも型に関する情報を書いていませんが、コンパイラは勝手に型推論を行い、append の型を [a] -> [a] -> [a] と推論してくれます(この型の読み方はまた後で)。

この強力な型推論機構は ML に由来するもので、これを実現するアルゴリズムは Hindley-Milner 型推論アルゴリズムとして知られています。

型クラス

Haskell の型システムは、型クラス (type class) と呼ばれる概念を採用している点で特徴的です。 型クラスというのはデータ型をカテゴライズする役割を持つ概念です。 例えば、数値型全般を表す Num 型クラスというものが存在します。 そして、具体的な数値型 IntDouble などが Num 型クラスに属します。

Numクラス

遅延評価

Haskell は 遅延評価 (lazy evaluation) と呼ばれる式の評価方式を採用しています。 これは、次のような評価方式のことをいいます。

遅延評価は、計算の効率化に役立つほか、無限長のデータの取り扱いにも役立ちます。

take 5 [1..]  -- 値: [1, 2, 3, 4, 5]

このプログラムは、無限長のリストに対して関数を適用し、リストの先頭 5 要素を取り出すというものです。 もし遅延評価でなかったら、このプログラムは引数の無限リストを評価しようとして暴走してしまうでしょう。

Haskell の仕様

1990 年に Haskell が発表されて以来、言語仕様は何回か改訂を重ねてきました。 直近では 2010 年に Haskell 2010 言語仕様が定められています。

言語仕様
1990 Haskell 1.0
1991 Haskell 1.1
1992 Haskell 1.2
1996 Haskell 1.3
1997 Haskell 1.4
1998 Haskell 98
2002 Haskell 98 Revised Report
2009 Haskell 2010

すべての言語仕様は、次のページから参照できます。

また、山下さんが管理されている次のサイトで、いくつかの仕様の邦訳が見れます。

現在は、Haskell Prime 2020 委員会が時期バージョンの策定に向けて動き始めているようです。

Haskell の実装

Haskell にはさまざまな実装が存在しますが1、現在は Grasgow Haskell Compiler (GHC) がデファクトスタンダードとしての地位を占めています。 このチュートリアルでも、開発環境として基本的には GHC を想定しています。

ビルドツール(パッケージ管理ツール)としては、CabalStack が使われます。 後発の Stack は、Cabal で問題となっていた、パッケージの依存関係地獄を解決するために生まれた存在です。 徐々にリプレースが進んでいます。

そして、上記の GHC、Stack、Cabal をまとめてインストールできる、Haskell Platform というディストリビューションがあります。 Haskell の開発環境を構築する際には、Haskell Platform をインストールするとよいでしょう。

関連サイト

Haskell でプログラムを開発する上で役立つサイトを紹介します。


  1. Haskell の実装に関する情報はここにまとめられています: Implementations - Haskell Wiki