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

入力の値から出力の値が決まる、という点では、演算子は関数と似ている。記法が違うだけで、演算子は意味的には引数を二つ取る関数だと考えることもできる。実際に、演算子を単独で括弧で囲うと、関数として使うことができる。

Prelude> (+) 1 2
3
Prelude> (-) ((*) 3 4) 7
5

エラー

入力する式を打ち間違えると、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という変数を使えるようになった。なお、このような状態を、変数dayの値が86400である、とか、変数dayが86400に束縛されている、と表現する。

*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 ()」になる。

なお、putStrLnは「文字列を出力して改行せよ」というコマンドを返す関数だが、このことを単に「putStrは文字列を出力して改行する関数だ」と表現することがある。いちいち「〜せよというコマンドを返す関数」というのが面倒だからだ。関数は計算したり何かを返したりすることはできてもそれ以外の動作を行うことはできないので、「〜する関数」という表現を見たら、それが実はコマンドを返す関数である可能性に注意してほしい。

コマンドの合成

putStrLnを使えば、好きな文字列を標準出力に表示して改行するプログラムが書ける*1。しかし、ふつうプログラムはもっと複雑なことをする。一行だけでなく何行も文字列を表示することもあるだろうし、途中で利用者から入力を受け付けることもあるだろうし、ファイルの内容を読んだりもするだろう。Haskellには、小さなコマンドを組み合わせて大きなコマンドにする方法があり、これを使っていくらでも複雑なプログラムを書けるようになっている。次のプログラムを見てほしい。

main :: IO ()
main = putStrLn "1st line" >> putStrLn "2nd line"

このプログラムを実行すると、「1st line」と表示して改行し、次に「2nd line」と表示して改行する。「>>」はコマンドを合成する演算子だ。aとbがコマンドなら、「a >> b」は「aを実行し、次にbを実行せよ」というコマンドになる。

合成されたコマンドもまたコマンドなので、新たな合成の材料にできる。「(a >> b) >> c」は、「「aを実行し、次にbを実行せよ」というコマンドを実行し、次にcを実行せよ」というコマンドだから、結局a、b、cを順に実行するコマンドになる。なお、これは単に「a >> b >> c」と書いてもよい。

このようなコマンドの合成は頻繁に使われるので、Haskellにはそのための構文が用意されている。上のプログラムは次のように書いても同じ意味だ。

main :: IO ()
main = do
  putStrLn "1st line"
  putStrLn "2nd line"

doという単語に続けて複数の式(ここでは二つ)を並べると、全体で一つの式(do式という)になる。並べるのはすべてコマンドを表す式でなくてはならない。並べた式の値を順番に合成したコマンドがdo式全体の値になる。

式を並べるとき、以下の二つの規則に従う必要がある。

これさえ守れば、字下げの量は自由だ。例えば、半角スペース四つで字下げしてみる。

main :: IO ()
main = do
    putStrLn "1st line"
    putStrLn "2nd line"

doの直後で改行しないスタイルもある。

main :: IO ()
main = do putStrLn "1st line"
          putStrLn "2nd line"

また、長い式を複数行に分けて書きたい場合、ある行を標準より多量に字下げすれば、前の行の続きとみなされる。例えば、以下の二つのdo式は同じ意味になる。

do
  a
  b c d
    e f
   g h i
  j

do
  a
  b c d e f g h i
  j

これらの規則を配置規則といって、Haskellではdo式の他にもいくつかの構文で使われている。

以上から分かるように、「do」という単語は文法上で特別の意味を持っているので、変数名などに使うことができない。こういう単語を予約語といって、以下のものがある。

case class data default deriving do else foreign if import in infix infixl infixr instance let module newtype of then type where _

コマンドの結果

プログラムが何か役に立つことをしようと思ったら、外部から何らかの情報を受け付けて、それを元に計算その他の動作をしないといけない。Haskellでは、外部からの入力を受け取るのにもコマンドを使う。

getLineは標準入力から一行読むコマンドだ。このコマンドが実行されると、読んだ文字列を結果として生成する*2。getLineは「IO String」という型を持つ。一般に、T型の結果を生成するコマンドの型は「IO T」になる。

実際にgetLineを実行してみよう。

main :: IO String
main = getLine

このプログラムを実行すると、プログラムはすぐに入力待ちに入るはずだ。そこに何らかの文字列をタイプしてEnterキーを押せば、プログラムは終了する。getLineの実行が終わって、したがってmainの実行が終わったからだ。mainが結果として生成した値は黙って捨てられる。

今はgetLineの結果を捨てたが、これを利用することを考えよう。例えば、これをputStrLnで表示してみる。これにはコマンドの合成を利用する。つまり、「getLineコマンドを実行し、その結果を標準出力に表示して改行せよ」というコマンドを作る。結果のあるコマンドを合成するには「>>=」という演算子を使う。

>>=演算子は>>に似ているが、左辺は結果を生成するコマンドで、右辺には「その結果を使って何をするか」を指定する。具体的には、>>=の右辺は、「値を受け取って、コマンドを返す関数」でなければならない。つまり、「a >>= f」という合成コマンドを実行するときは、まずaを実行し、結果(rとしよう)を得て、次に「f r」というコマンドを実行する。

今、getLineの結果を使ってするべきことは、それを表示して改行することなので、>>=の右辺にはputStrLnを指定する。プログラムは以下のようになる。

main :: IO ()
main = getLine >>= putStrLn

これを実際に実行して、入力した文字列がそのまま表示されることを確かめてほしい。

具体的に何が起こるか見ておこう。「getLine >>= putStrLn」というコマンドが実行されるとき、まず>>=の左辺のコマンド、getLineが実行されるので、プログラムは入力待ちに入る。ここで例えば「tow」と入力したとすると、getLineの実行は終了し、結果として"tow"が生成される。次に、この結果に右辺を適用した「putStrLn "tow"」が実行される。これは、「標準出力に「tow」と表示して改行せよ」というコマンドだから、期待した通りの結果になる。

この種の合成も、do式を使って書ける。例えば、このプログラムは次のように書いても同じだ。

main :: IO ()
main = do
  line <- getLine
  putStrLn line

一般に、do式中に式を並べるとき、式を書く代わりに「変数 <- 式」という形のものを書いてもよい。この場合、コマンドは全体として「式が表すコマンドを実行し、得られた値に変数を束縛し、残りのコマンドを実行する」というものになる。この例なら、do式の値は「getLineを実行し、その結果にlineを束縛し、putStrLn lineを実行せよ」というコマンドになる。

ところで、上で「T型の結果を生成するコマンドの型はIO Tだ」と書いた。では「IO ()」という型は何かというと、そのまま「()型の結果を生成するコマンドの型」だ。()型は、単位型とかユニット型とも呼ばれるが、この型に属する値は一つしかない。その唯一の値も()と書く。値が一つしかないので、()型のデータがあっても全く情報にならない。つまり、putStrLnが返すコマンドのように、一見結果を生成しないコマンドは、実際には()という無意味なデータを生成していることになる。

GHCiとコマンド

コマンドは、関数と同じく、表示できないデータだ。しかし、GHCiにコマンドを表す式を入力してもエラーにならない。

Prelude> putStrLn "Helium"
Helium

これは、GHCiがコマンドを特別扱いするためだ。通常、GHCiは、入力された式を評価し、得られた値を表示しようとするが、「IO T」という形の型の式が入力された場合に限り、評価して得られたコマンドをその場で実行する。その後、Tが()以外で、しかも表示できる型なら、生成された結果も表示する。例えばGHCiに「getLine」と入力すると次のようになる。

Prelude> getLine
infornography
"infornography"

これは便利な機能だが、慣れないうちは混乱の原因になることがある。

文字列

文字列は、外部とのやりとりをするのに重要なデータだ。標準入出力を読み書きするのには文字列を使うし、ファイルに書き込むときも文字列を使う。この節では、Haskellでの文字列の扱いについて基本的なところを説明する。

文字列リテラル

文字列は文字の並びだと前に紹介した。Haskellでいう文字とは、Unicodeコードポイントのことだ。厳密な解説はweb上の資料を参照してほしいが、おおざっぱに、ラテンアルファベット、平仮名、片仮名、漢字、アラビア文字、ハングル、キリル文字など、世界中で使われているあらゆる文字が含まれる。また、アラビア数字や、普通にPCで扱えるあらゆる記号が表現できる。例えば、"図4α"は、「図」「4」「α」の三つの文字からなる文字列だ。

このような目に見える文字の他に、特殊な効果を持つ文字群があって、制御文字と呼ばれる。Unicodeには無数の制御文字があるが、頻繁に使われるのは改行とタブの二つだけだ。改行文字はその名の通り、行を区切るのに使われる。例えば、putStrLnは文字列を出力して改行するが、これは文字列を出力したあとに改行文字を出力しているだけだ。タブ文字は、後続のテキストを次の8の倍数桁目(表示する環境によっては8以外の値もありえる)までずらして表示させるものだ。

既に使っているが、プログラム中で文字列の値を書き表すには、その文字列を"abc"のように二重引用符で囲んで表現する。このようなものを文字列リテラルと呼ぶ。ちなみに、リテラルとは値をそのまま書き表したもののことだ。だから、「0」「1」「76」などもリテラルで、数値リテラルという。

文字列リテラル中に制御文字を直接書くことはできない。例えば、次のようなものは、文字列リテラル中に改行があるので文法違反になる。

s :: String
s = "温情
判決"

これは、次のように書かないといけない。

s :: String
s = "温情\n判決"

このように、制御文字を含んだ文字列リテラルを書きたいときは、「\」から始まる特殊な表記を使う。このような表記をエスケープコードという。良く使われるのは、改行を意味する「\n」と、タブを意味する「\t」だ。また、文字列リテラル中に単純に二重引用符を書くと、文字列リテラルの終わりと見なされるので、二重引用符を含む文字列を書きたい場合は「\"」というエスケープコードを使う。最後に、「\」という文字自体を表したいときは「\\」と書く。

Unicodeコードポイントを直接番号で指定することもできる。例えば、"鱈"と"\40008"は同じ文字列を表す。十六進で"\x9c48"と書いたり、八進で"\o116110"と書いてもよい。ASCII(いわゆる半角英数)の範囲外の文字をGHCiで表示すると、この形式が使われる。

Prelude> "用心棒"
"\29992\24515\26834"

なお、ソースファイル中のコメント以外の場所でASCIIの範囲外の文字を使うなら、ソースファイルの文字コードをUTF-8にする必要がある。近代的なエディタなら、保存するときにファイルの文字コードを選べるようになっているだろう。

文字列操作

演算子++を使うと、文字列を連結できる。

Prelude> "rear" ++ "range"
"rearrange"

これを使うと、例えば、名前を尋ねて、その名前で挨拶するプログラムが書ける。

main :: IO ()
main = do
  putStrLn "What's your name?"
  name <- getLine
  putStrLn ("Hello, " ++ name ++ ".")

関数lengthは、文字列の長さ(文字数)を返す。

Prelude> length "天上天下唯我独尊"
8
Prelude> length "a\nb"
3

整数と文字列の相互変換

show関数を使うと、整数を10進表記の文字列に変換できる。

Prelude> show (7 * 8)
"56"

これを使うと、整数を使って計算し、その計算結果を出力するということができる。

day :: Int
day = 60 * 60 * 24

main :: IO ()
main = putStrLn ("1 day = " ++ show day ++ " seconds.")

read関数は逆に、10進表記された文字列を整数に変換する。例えば、次のプログラムは、標準入力から整数を受け付け、その自乗を表示する。

main :: IO ()
main = do
  putStrLn "Input an integer."
  input <- getLine
  putStrLn ("The square of " ++ input ++ " is " ++ show (square (read input)) ++ ".")

square :: Int -> Int
square = \x -> x * x

実行例を載せておく。

Input an integer.
440
The square of 440 is 193600.

なお、readをGHCiから単独で使おうとすると、曖昧エラーが発生したり、評価時に失敗したりする。

Prelude> read "4"

<interactive>:1:0:
    Ambiguous type variable `a' in the constraint:
      `Read a' arising from a use of `read' at <interactive>:1:0-7
    Probable fix: add a type signature that fixes these type variable(s)

これは、readが実は整数以外の型も扱えるようになっていて、単純な「String -> Int」の関数でないことに原因がある。これについて詳しくは後で説明するつもりだが、今のところは、次のようにreadIntを定義して、readを使ってエラーが発生したら代わりにreadIntを使うようにすれば問題ない。

readInt :: String -> Int
readInt = read

引数として与えられた文字列が整数として解釈できない場合、readは失敗して、評価時のエラーが発生する。

*Main> readInt "4"
4
*Main> readInt "-4"
-4
*Main> readInt "4a"
*** Exception: Prelude.read: no parse
練習問題
標準入力から秒数を受け取って、それが何日と何時間に当たるかを出力するプログラムを書け。

リンク

公式

処理系

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

日本語サイト

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


*1 ただし、GHC 6.8.2の時点ではputStrLnでそのまま日本語を表示できない。日本語の扱いについては後述するつもり。
*2 関数の「結果」と同じ用語だが、違うものなので注意

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