Haskell

能書き

Haskellの特徴

Haskellのおもな特徴を列挙すると次のようになる。この手の言語に慣れていない人には意味の分からないところがあるかもしれないが、それは筆者手を抜いて説明を省いているからなので、適当にスルーしてほしい。

関数型言語って何

Haskellは純粋関数型言語に分類される。関数型言語というのは、関数的プログラミングがしやすいような言語、という意味だ。では関数的プログラミングとは何というと、これは手続き的プログラミングと比較してみるのが良い。

現在主流の言語は、ほぼ全て手続き型言語だ。手続き的プログラミングでは、コンピュータに単純な命令を出すことができて、それをたくさん並べ、組み合わせることでプログラムを書く。例として、次の問題を考えてみる。

1からnまでの整数の中で、2でも3でも割り切れないものの総和を求めよ。

整数を6で割った余りに着目する賢い方法もあるが、ここでは素朴に、1からnまでの全ての整数について、2か3で割り切れるか試し、テストを通過したものを全て足し合わせる方法を取ることにする。手続き的プログラミングでは、この問題を解くプログラムは大体次のようになる。

「総和」を0とする。
「現在の整数」が0からnまで動く間、以下の一行を繰り返す:
 もし、「現在の整数」が2でも3でも割り切れないなら、「総和」に「現在の整数」を足す。
この時点で「総和」が問題の答えになっている。

このプログラムには、「「総和」を0とする」とか、「「総和」に「現在の整数」を足す」という命令が使われている。これらの命令を、繰り返しや条件判断(もし~ならば~する)と組み合わせることで、プログラムが構成されている。プログラムは、原則として、上にある命令から順に実行される。このプログラムでは、実行が進むにしたがって、「総和」の値は刻々と増えていく。このように、変化する値(変数)を使うのが命令型プログラミングの特徴のひとつだ。

一方、関数的プログラミングでは、データに着目する。何か問題を解くプログラムを書くときは、入力データと出力データを考えて、入力データが与えられたとして、出力データがどうなるかを記述する。これはちょうど、入力データから出力データへの(数学的な)関数を書き下すことに相当する。例えば、上の問題を関数的プログラミングで解いてみると次のようになる。

1からnまでの整数の列をAとする。
Aから、2でも3でも割り切れない整数のみを取り出した列をBとする。
Bの要素を全て足し合わせたものが問題の答えである。

このプログラムには命令はどこにも出てこない。ただ入力と出力の関係を述べているだけだ。変数(変化する値)も使われていない。あるのは、入力データ、出力データ、AやBという中間データと、それらをどのように定めるかの規則だけだ。

このふたつのスタイルの違いは、言語の違いにそのまま対応する訳ではない。関数的プログラミングのできる手続き型言語は多いし、逆に、関数型言語は大抵、手続き的プログラミングもできるようになっている。それでも、どっちに基礎を置くかの違いはあって、細かい部分のスタイルには大きな違いがあるので、どちらか一方しか知らない人はもう一方を学んでみると新鮮な驚きがあるかもしれない。

手続き型言語は、現実のコンピュータと相性の良い構造になっている。CPU自体、命令を次々と処理していくように作られているからだ。これに対して、関数型言語は、抽象的・数学的な計算や記号操作を基礎にしている。Haskellは「純粋」な関数型言語という位で、とくにこの傾向が強い。

おすすめ

上のような特徴から、少なくとも以下のような人にはHaskellをおすすめできる。

処理系を導入する

入れる処理系を選ぶ

2007年7月の時点では、以下の処理系がメンテナンスされている。

GHC
対話環境とインタプリタとネイティブコンパイラのセット。
Hugs
対話環境とインタプリタのセット。対話環境にはWindows用GUIも付属している。

ここでは、利用者の多いと思われるGHCのインストールを説明する。

GHCのインストール

Windows

GHCのダウンロードページの「Current Stable Release」のリンクをたどり、「Windows (x86) (standalone)」のところからインストーラ(msiまたはexe)をダウンロードして実行する。インストール終了後、「<GHCをインストールしたディレクトリ>\bin」(デフォルトではC:\ghc\ghc-<バージョン番号>\bin、インストーラの最後のダイアログボックスで確認できる)を環境変数PATHに加える。

コマンドプロンプトを開き、「ghc --version」と入力してみる。

The Glorious Glasgow Haskell Compilation System, version 6.8.2

こんな感じのものが表示されれば成功。

Arch Linux, Debian, Fedora, FreeBSD, Gentoo, MacOS X, NetBSD, OpenBSD, openSUSE, Ububtu

OSごとにパッケージが用意されている。詳しくはGHC: Distribution Packagesを参照。

その他のプラットフォーム

GHCのダウンロードページからバイナリパッケージを入手してインストール。なお、GHCを普通にビルドしようとするとGHCが必要なので、初めてのインストールではバイナリを使うことを勧める。

チュートリアル

式と評価

コンピュータは計算する機械だ。これに合わせて、Haskellも計算を基礎としている。もちろん、コンピュータにできることは計算以外にもある。たとえば画面に文字を表示したりネットワークで通信したりだ。しかし、Haskellでは、こういう色々な動作を扱うときも一種の計算を使う。だからHaskellでは計算が特に重要で、この節ではHaskellでの計算について説明する。

対話環境

実際にHaskellで計算をしてみよう。まずGHC付属の対話環境、GHCiを起動する。コマンドラインから「ghci」コマンドで起動するか、Windowsならスタートメニューから「Glasgow Haskell Compiler」→「GHCi」でも良い。起動すると次のような画面が出てくるはずだ。

GHCi, version 6.8.2: http://www.haskell.org/ghc/  :? for help
Loading package base ... linking ... done.
Prelude>

最後の行は「Prelude>」となっていて、行末にカーソルがあり、ここに入力できるようになっている。試しに足し算を計算させてみよう。「33 + 34」と半角で入力し、エンターキーを押す。すると以下のようになる。

Prelude> 33 + 34
67
Prelude> 

こちらの「33 + 34」という入力に対して、GHCiは「67」という結果を返してきた。そして再び「Prelude>」と表示して入力を待っている。このように、GHCiは一つ入力を受け取るたびに応答を返す。これは人間同士が対話するのに似ている。GHCiが対話環境とか対話的インタプリタとか呼ばれるのはこのためだ。

さて、この場合、GHCiは「33 + 34」を計算して結果「67」を得た。「33 + 34」のように、何を計算すべきか表したものを(Haskellの)と呼び、その計算の結果をその式のと呼ぶ。また、式を計算して値を得ることをその式を評価するという。だから、この場合、式「33 + 34」を評価して値「67」を得た、という言い方をする。

GHCiを終了させたいときは「:quit」または短縮して「:q」と入力する。このように、GHCi自体への命令はコロンから始まるコマンドで行う。コマンドの一覧は「:?」というコマンドで確認できる。

演算子と関数

Haskellでは足し算以外にも多くの種類の式を扱うことが出来る。例をいくつか見ていこう。

まず、足し算と同じように引き算も使える。

Prelude> 33 - 34
-1

この二つは自由に組み合わせることができる。

Prelude> 3 + 6 - 1 - 4
4

スペースはあってもなくても良い。

Prelude> 3+6        - 1-  4
4

単独の整数は、それ自体で式になる。

Prelude> 7
7

掛け算は「*」で表す。

Prelude> 2 * 7
14

数学と同じように、掛け算は加減算よりも優先される。これに逆らった式を作りたいときは括弧が使える。

Prelude> 1 + 2 * 3
7
Prelude> (1 + 2) * 3
9

括弧を多重に使いたいときは、そのまま入れ子にすれば良い。

Prelude> (14 - (1 + 2)) * 4
44

「+」とか「-」とか「*」のような記号を演算子と呼ぶ。

除算は少し勝手が違う。整数の除算の結果は商と余りの二つで表されるからだ。Haskellでは、「A」を「B」で割ったときの商は「div A B」、余りは「mod A B」と書く。

Prelude> div 7 3
2
Prelude> mod 7 3
1

divやmodは関数だ。Haskellにおいて関数とは、数学でいう関数とほとんど同じで、入力から出力を決める規則のことだ。例えばdivは、「被除数」と「除数」の二つを入力として出力「商」を決める。関数への入力のことを関数の引数(ひきすう)といい、左から順に第一引数、第二引数などと呼ぶ。出力のことは結果という。また、関数Fに引数としてAを指定して結果を得ることを、「FをAに適用する」とか、「FをAで呼び出す」とか言う。例えば上の例では、divを7と3に適用している。

Haskellで関数適用を表現する時は、「div 7 3」のように関数と引数列をそのまま並べて書き表す。上で「*」は「+」よりも優先されると書いたが、関数適用はあらゆる演算子より優先順位が高い。だから、「div 7 3 + 1」は「(div 7 3) + 1」という意味にとられる。

Prelude> div 7 3 + 1
3
Prelude> div 7 (3 + 1)
1

divやmodは引数を二つ取る関数だが、そうでない関数もある。例えばnegateは引数を一つ取る関数で、与えられた数の符号を反転したものを返す。(ある関数が値vを結果とするとき、その関数はvを返す、と言う)

Prelude> negate 4
-4
Prelude> negate (2 - 5)
3

エラー

入力する式を打ち間違えると、GHCiはエラーメッセージを出力する。読み方について簡単に見ておこう。

式が入力されると、GHCiはまず式の構造を解析する。どのカッコとどのカッコが対応しているか、とか、関数適用の引数はどの部分だ、とかを確定するわけだ。これを構文解析(parsing)という。構文解析エラー(parse error)はこれに失敗したというエラーのことで、括弧の対応が間違っていたり、演算子の引数が存在しなかったりするときに発生する。

Prelude> 4 +
<interactive>:1:3: parse error (possibly incorrect indentation)
Prelude> (2 + 3)) * 4
<interactive>:1:7: parse error on input `)'

式が構文解析に成功して、GHCiが構造を把握すると、次に「型検査」が行われる。型については後で詳しく説明するつもりだが、大雑把にいうと、それぞれの部分式(式の中の式)が正しい使われ方をしていることを確認する。例えば、整数と整数を足すことはできるが、関数と関数を足すことはできない、という具合だ。型検査に失敗すると型エラーが出力される。

Prelude> div + mod
 
<interactive>:1:0:
    No instance for (Num (a -> a -> a))
      arising from use of `+' at <interactive>:1:0-8
    Possible fix: add an instance declaration for (Num (a -> a -> a))
    In the expression: div + mod
    In the definition of `it': it = div + mod

「No instance for...」というエラーメッセージが型エラーを示している。

構文的ミスによって型エラーが起きることもある。括弧をつけ忘れたり、関数の引数の数を間違えたりすると、意図に合わない形で構文解析が成功し、型検査の段階で始めて間違いが分かる。例えば、「34を3で割った商を2で割った商」を表す式は「div (div 34 3) 2」だが、誤って括弧を省くと、構文エラーにはならずに型エラーとして報告される。

Prelude> div div 34 3 2

<interactive>:1:0:
    No instance for (Integral (a -> a -> a))
      arising from use of `div' at <interactive>:1:0-13
    Possible fix:
      add an instance declaration for (Integral (a -> a -> a))
    In the expression: div div 34 3 2
    In the definition of `it': it = div div 34 3 2

この場合、関数「div」がdiv, 34, 3, 2という4つの引数を伴って呼ばれていると解釈されている。

型検査に成功すれば、正しいHaskellの式だと確認されたことになり、実際に評価が始まる。評価中に発生するエラーもある。これまでに紹介した範囲の式では、これが発生する場合は一つだけで、整数を0で割ろうとしたときだ。

Prelude> div 6 (3 - 3)
*** Exception: divide by zero

評価中に発生したエラーは「*** Exception: ...」と報告される。

なお、例えば「div」を単独で評価しようとすると以下のようにエラーになるはずだ。

Prelude> div

<interactive>:1:0:
    No instance for (Show (a -> a -> a))
      arising from a use of `print' at <interactive>:1:0-2
    Possible fix: add an instance declaration for (Show (a -> a -> a))
    In the expression: print it
    In a 'do' expression: print it

これは「div」が式として間違っているというのではなく、「div」という関数を表示しようとして表示できないので失敗しているだけだ。Haskellの値には、表示できるもの(整数など)と表示できないもの(関数など)があって、表示できないものを表す式をGHCiで評価しようとするとこのようなエラーが発生する。

定義

ソースファイルと変数

なんらかの理由で、百万秒が何日間に相当するか知りたくなったとしよう。これをGHCiで計算すると、人によって微妙なやりかたの違いはあるだろうが、大体次のような感じになるだろう。

Prelude> 60 * 60 * 24 -- 一分が60秒で、一時間が60分で、一日が24時間だから、一日は何秒か?
86400
Prelude> div 1000000 86400 -- 一日が86400秒だから、百万秒は何日か?
11

これで、端数を切り捨てて十一日だということが分かった。なお、上で「--」(連続する半角マイナス二個)に続けて説明を書いたが、「--」とそれに続く部分はコメントといって、プログラムとしては無視される。主に人間が読むための説明を書くのに使う。

では、逆に、14日間は何秒かを知りたくなったとしよう。一日が86400秒であることが既に分かっているので、次のように計算できる。

Prelude> 14 * 86400
1209600

もし、この手の計算を何度もしなければならないなら、その都度86400という値を打ち込むのは面倒だ。忘れたら計算し直さなければならないし、タイプミスしたら結果がおかしくなる。このような場合のために、Haskellでは、値に名前を付けておいて、あとからその名前で値を参照するということができる。

値に名前を付けたもののことを変数という。変数を使うには、予めどの値にどんな名前を付けるか指定しなければならない(これを変数の定義という)が、これはHaskellソースファイルというファイルを用意することで行う。(Haskellソースファイルというのは、書いたHaskellプログラムを入れておくファイルのことでもある。後で見るように、Haskellプログラムというのはほとんど定義の集まりのようなものだ)

Haskellソースファイルの中身はテキストファイルだが、拡張子は.hsとする。例として、86400という値にdayという名前を付けてみよう。言いかえると、これから値86400を持つ変数dayを定義する。これには、適当な名前、例えばday.hsというソースファイルを用意して、以下の内容を書き込む。

day :: Int
day = 86400

これをGHCiから使うには、:loadコマンド(省略形は:l)でロードする。(TODO:カレントディレクトリについて書く)

Prelude> :load day.hs
[1 of 1] Compiling Main             ( day.hs, interpreted )
Ok, modules loaded: Main.
*Main> 

GHCiをコマンドラインから起動しているなら、起動時の引数としてファイル名を渡してもよい。

これで、式のなかで86400の代わりにdayという変数を使えるようになった。

*Main> day
86400
*Main> day + 1
86401
*Main> div 8000000 day
92

これでいちいち長い数値をタイプする必要はないし、この数値を覚えておく必要もないし、ミスタイプしたらエラーを報告してくれる。

なお、day.hsをロードした状態でこれを編集したら、:reloadコマンド(省略形:r)で再ロードすれば、変更点が反映される。

さて、ソースファイルの中身を解説しよう。day.hsを再掲する。

day :: Int
day = 86400

二行目は、変数dayを、値86400を持つ変数として定義する、という意味だ。一行目は、変数dayのを指定している。型とは、値の種類のことで、その値をどういうふうに扱えるかを決めるものだ。(整数と整数は足せるが、関数と関数を足そうとすると型エラーになったことを思い出すとよい)。ここでは、dayは整数なので、それを意味するInt(integerの略)を指定している。なお、型の名前はIntのように必ず大文字から始まり、変数の名前はdayのように小文字から始まらなくてはならない。

今回、一日の秒数をGHCiで予め計算して、その値を使ってdayを定義したが、実はこれは必要ない。変数を定義する等式の右辺には任意の式が書ける。よって、day.hsを以下のように書き換えても意味は変わらない。

day :: Int
day = 60 * 60 * 24

一つのソースファイルには、いくつでも変数の定義を書くことができる。また、変数の定義の中で別の変数を使ってもよい。例えば、次のソースファイルは三つの変数を定義する。

-- 一分の秒数
minute :: Int
minute = 60

-- 一時間の秒数
hour :: Int
hour = minute * 60

-- 一日の秒数
day :: Int
day = hour * 24

このように、ソースファイル中には、コメントや空行を自由に入れてよい。

複数の定義を書く場合、順番はどうでもいい。例えば、上のファイルを以下のように書き換えても意味は同じだ。

-- 一日の秒数
day :: Int
day = hour * 24

-- 一時間の秒数
hour :: Int
hour = minute * 60

-- 一分の秒数
minute :: Int
minute = 60

関数

ここまでに定義したdayとhourを使えば、例えば220万秒が何日と何時間に当たるか計算できる。

*Main> div 2200000 day -- 220万秒は何日か?
25
*Main> mod 2200000 day -- 残りの秒数は?
40000
*Main> div 40000 hour -- 40000秒は何時間か?
11

よって、220万秒は25日と11時間だ。しかし、秒数が何日何時間に当たるかの計算を何度もしなくてはならないなら、これでもまだ面倒だ。「秒数が与えられたとき、それが何日と何時間になるか計算する」という機能をまとめて名前を付けることを考えよう。

このような機能は、関数を使うとうまく表現できる。前に説明したように、Haskellの関数は引数(入力)から結果(出力)を決める規則のことだった。ここでは、引数として秒数が与えられると、それが何日と何時間になるかを結果とするような関数を作ればよい。関数から「何日」と「何時間」を両方返すようにするのは少々ややこしいので、とりあえず秒数から日数への関数を作ることにしよう。

この関数の結果は、例えば引数が100000ならdiv 100000 day、引数が0ならdiv 0 dayだ。一般に、引数をsecsとすると、結果はdiv secs dayと書ける。これをもとに、この関数を次のように書く。

\secs -> div secs day

つまり、関数を作るには、まず、何か変数を適当に決めて(この例ではsecs)、引数の値がその変数に入っていると仮定して、結果を式で表す。その上で、「\変数 -> 式」という形のものを書けば、それが求める関数になっている。

ここでのsecsのような変数を仮引数という。仮引数の名前は何でもいい。この関数を\xyz -> div xyz dayと書いても意味は変わらない。分かりやすいと思う名前を付ければよい。このような形で関数を表す式を関数抽象という。

さて、こうして作った関数は、通常の関数と全く同じように適用できる。

*Main> (\secs -> div secs day) 2200000
25
*Main> (\secs -> div secs day) 1200000
13

これで無事に関数を作ることができたので、あとは名前を付けるだけだ。関数は一種の値なので、値に名前を付ける方法、つまり変数定義が使える。例えばsecsToDaysという名前にするなら、days.hsに以下の二行を追加する。

secsToDays :: Int -> Int
secsToDays = \secs -> div secs days

二行目はいつもどおりの変数定義だ。一行目で「Int -> Int」という型を指定しているが、これは、引数の型がIntで、結果の型がIntである関数、のことだ。一般に、引数の型がAで、結果の型がBであるような一引数関数の型は「A -> B」と書く。

これをGHCiにロードすれば、秒数から日数への変換が簡単にできる。

*Main> secsToDays 1234567
14
*Main> secsToDays 6666666
77
練習問題
与えられた秒数が何日何時間に相当するか、という問題のうち、「何日」の部分は既に関数として定義した。同様に、残りが何時間であるかを計算する関数を書け。次に、その関数を値とする、secsToRemainingHoursという名前の変数を定義せよ。

多引数関数

ついでに、引数を二つ以上取る関数の書き方も見ておこう。関数抽象では、「\」と「->」の間に、必要なだけの仮引数を、空白で区切って並べる。例えば、二つの整数の平均を(端数切り捨てで)計算する関数は次のようになる。

\x y -> div (x + y) 2

この関数の型は「Int -> Int -> Int」になる。一般に、N引数の関数の型は、「第一引数の型 -> 第二引数の型 -> ... -> 第N引数の型 -> 結果の型」と書く。

プログラム

これで準備が整ったので、実際に動作するプログラムを書く方法を説明する。

Hello, world

次のソースコードをhello.hsとしよう。これは、標準出力に「Hello, world」と表示して改行するプログラムだ。

main :: IO ()
main = putStrLn "Hello, world"

コマンドラインで、hello.hsのあるディレクトリに移動し、次のコマンドを実行する。

ghc --make hello.hs

すると、メッセージが表示され、同じディレクトリにhello(Windowsならhello.exe、古いバージョンのGHCではa.outまたはa.exe)という実行ファイルができるはずだ。これを、「./hello」(Windowsなら単にhello)として実行すると、「Hello, world」という文字列が表示される。

なお、Windowsで、エクスプローラからhello.exeを起動すると、黒い窓が一瞬だけ現れてすぐに消えるので、結果を確認しにくい。これは、「Hello, world」を出力したあとすぐにプログラムが終了するからだ。

今、ghcコマンドで実行ファイルを生成してからそれを実行するという手順をとったが、runghcコマンドを使えばソースファイルを直接実行できる。

$ runghc hello.hs
Hello, world

さて、hello.hsを解説しよう。再掲する。

main :: IO ()
main = putStrLn "Hello, world"

前節を読んだなら分かるとおり、これは「main」という変数を一つ定義するだけのソースファイルだ。mainは特別な変数で、mainの値によってプログラムが何を行うか決まる。mainという名前の変数が定義されていないソースファイルは、独立したプログラムとしてコンパイルしたり実行したりできない。

mainの型は「IO ()」と指定されている。これはmainの値がIOコマンドだということだ。IOコマンド(IO動作とかIOアクションとも呼ばれる)とは、プログラムが外界に対して何をするかの指示だ。例えば、「画面に「A」という文字を表示せよ」とか「date.txtというファイルを作成して、今日の日付を書き込め」とか「スピーカから900Hzの正弦波を1秒間出力せよ」とか、その他プログラムができるあらゆることに対応するコマンドがありえる。

Haskellプログラムが実行されるときは、変数mainの値がコマンドとして実行され、それが終わるとプログラムの実行も終わる。

このプログラムでは、mainの値は「putStrLn "Hello, world"」と指定されている。実行結果からも分かるように、この式の値は「「Hello, world」と標準出力に表示して改行せよ」というコマンドだ。どうしてそうなるのか見ていこう。

"Hello, world"というのは、そのまま「Hello, world」という文字列を表す。文字列がどういうものかは後で詳しく説明するつもりだが、とりあえず文字が並んだものだと考えてほしい。この例なら、"Hello, world"は12の文字が並んだ文字列だ。プログラム中では文字列は二重引用符で囲って書き表す。文字列の型はStringという。

putStrLnはコマンドを作る関数だ。引数が文字列xなら、結果は「xを標準出力に表示して改行せよ」というコマンドになる。だから、「putStrLn "abc"」なら「標準出力に「abc」と表示して改行せよ」というコマンドだし、「putStrLn "Hello, world"」なら「標準出力に「Hello, world」と表示して改行せよ」というコマンドだ。引数が文字列で結果がIOコマンドである関数だから、putStrLnの型は「String -> IO ()」になる。

リンク

公式

処理系

GHC
http://www.haskell.org/ghc/
Hugs
http://www.haskell.org/hugs/

日本語サイト

http://www.sampou.org/cgi-bin/haskell.cgi


トップ   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS