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
練習問題
標準入力から秒数を受け取って、それが何日と何時間に当たるかを出力するプログラムを書け。

真偽値

プログラムでは、特定の条件が満たされているかどうかによって別のことをする、という条件分岐がよく使われる。この節では、Haskellで条件を扱うためのデータである真偽値について解説する。

if式

次に示すのは、秒数を入力してもらって、それが何日間に相当するか表示するプログラムだ。

main :: IO ()
main = do
  putStrLn "Input a duration in seconds."
  input <- getLine
  putStrLn (input ++ " seconds = " ++ show (secsToDays (read input)) ++ " days.")

secsToDays :: Int -> Int
secsToDays = \secs -> div secs day
day :: Int
day = 60 * 60 * 24

このプログラムを実行し、「100000」を入力すると、「100000 seconds = 1 days.」と表示される。計算は正しいものの、一日なのに複数形の「days」が使われている。実用上支障はないので無視してもいいのだが、ここでは単数形と複数形を正しく使い分けることを考えよう。

mainの定義にむやみに複雑な式を書くのを避けるため、日数からそれを表す文字列を計算する部分を、一つの関数として独立させることにしよう。この関数をformatDaysと呼ぶことにすると、これは次のように定義できる。

formatDays :: Int -> String
formatDays = \n -> show n ++ " days"

いまのところ、「formatDays 4」は"4 days"に、「formatDays 1」は"1 days"になる。これを、「formatDays 1」が"1 day"になるようにしたい。これには、formatDaysの定義を次のよう書き換える。

formatDays = \n -> show n ++ if n < 2 then " day" else " days"

元の定義と見くらべると、単に「" days"」となっていたところが「if n < 2 then " day" else " days"」に変わっていることが分かると思う。これは、「nが2より小さければ" day"、そうでなければ" days"」という意味の式だ。こういう式はif式と呼ばれ、「if 条件 then 式1 else 式2」という形をとる。この式の値は、条件が成り立つなら式1と同じで、そうでないなら式2と同じになる。if、then、elseは全て予約語だ。

いくつか例を試してみよう。

Prelude> if 1 < 2 then "T" else "F"
"T"
Prelude> if 1 < 1 then 1 else 2
2
Prelude> (if 1 > 3 then 5 else 7) * 9
63

比較演算

ここまで、if式の条件部として「n < 2」のような不等式を説明なしに使ってきたが、実はこれも普通のHaskellの式にすぎない。GHCiで試してみよう。

Prelude> 1 < 2
True
Prelude> 1 < 1
False
Prelude> 3 + 8 < 9 * 2
True

このように、不等号「<」は通常の演算子で、その結果はTrueかFalseになる。この二つの値を総称して真偽値という。真偽値の型はBoolだ。

if式の条件部には、型がBoolでさえあればどんな式を書いてもよい。その式の値がTrueなら条件成立、Falseなら不成立と見なされる。

Prelude> if (if True then False else True) then 1 else 2
2

「<」と同じように不等号「>」も使える。また、≦は「<=」、≧は「>=」、=は「==」、≠は「/=」とそれぞれ書く。

Prelude> 4 >= 4
True
Prelude> 4 == 4
True
Prelude> 1 + 3 /= 2 * 2
False

論理演算

「xが十進で二桁の自然数である」という条件を表すことを考えよう。xが二桁である条件は、それが10以上100未満であることだ。しかし、これを「10 <= x < 100」と書くことはできない。Haskellの不等号は単なる演算子であって、二つの値を比較するのにしか使えない。

代わりに、この条件を二つに分解する。「xが10以上である」かつ「xが100未満である」と考える訳だ。この二つの条件はそれぞれ「10 <= x」「x < 100」と書ける。これを「かつ」でまとめるには演算子&&を使う。結局、この条件は「10 <= x && x < 100」と書ける。

&&演算子の両辺は真偽値でなければならず、その両方がTrueだったときのみ結果もTrueになる。これは二つの条件を「かつ」でまとめるのに対応する。「または」に対応する演算子は||で、これは両方がFalseだったときのみ結果もFalseになる。また、関数notは、引数がTrueであれば結果はFalse、引数がFalseであれば結果はTrueで、論理否定に対応する。これらの型は次の通り。

(&&) :: Bool -> Bool -> Bool
(||) :: Bool -> Bool -> Bool
not :: Bool -> Bool

この記法について説明しておく。変数定義の場合と同様に、「A :: T」というのは「Aの型はTである」と読む。また、演算子の型を記述するときは、演算子を括弧で囲むと関数として扱えることを利用する。この場合、&&は両辺ともにBool型の式をうけつけ、結果もBool型である、ということになる。

練習問題
前に挙げた「名前を尋ねて、その名前で挨拶するプログラム」を改造し、名前として空文字列が入力されたときには単に「Hello.」とだけ出力するようにせよ。

do式の中でif式を使う

(TODO: 書く)

便利な構文

関数定義

実際のHaskellプログラムでは、関数は極めて頻繁に作られ、関数を値にもつ変数も大量に定義される。普通、ソースファイルの大部分が、関数を値とする変数の定義で占められるほどだ。このために、こういう変数を定義するための特別な構文が用意されていて、特に簡潔に定義できるようになっている。

次の定義は、整数の符号を反転する関数を、通常の記法で書いたものだ。

myNegate :: Int -> Int
myNegate = \n -> 0 - n

専用の構文を使うと、この定義を次のように書ける。

myNegate :: Int -> Int
myNegate n = 0 - n

型指定の部分は変わっていない。違うのは、関数抽象のバックスラッシュがなくなって、仮引数が等式の左辺に移動したことだ。この記法は、直感的には、「myNegate n」が「0 - n」に等しくなるようにmyNegateを定義している、と解釈できる。数学での関数定義の記法「f(n) = 0 - n」にも近い。

引数を複数とる関数も同じように定義できる。次の示すのは、二つの引数の差の自乗を計算する関数だ。

diffSquare :: Int -> Int -> Int
diffSquare a b = (a - b) * (a - b)

この構文を使った定義は、局所的・機械的な手順で通常の定義に書き換えることができる。つまり、便利な略記法に過ぎない。こういう構文を構文糖衣という。例えばdo式は、(>>)と(>>=)などを使った形に書き換えられるので、構文糖衣の一つだ。

where節

上のdiffSquareの定義を再掲する。

diffSquare :: Int -> Int -> Int
diffSquare a b = (a - b) * (a - b)

この定義には「a - b」という式が二回出現している。これを一つにまとめることを考えよう。ひとつの方法は、整数を自乗する関数を定義することだ。

diffSquare :: Int -> Int -> Int
diffSquare a b = square (a - b)

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

もうひとつ方法がある。局所変数を利用するものだ。同じ値を何度も使う必要があるとき、その値を持つ変数を定義するのが常套手段だった。ここでは、「a - b」という値を繰り返し使いたい。しかし、aもbもdiffSquareの仮引数だから、値が一つに決まらない。こういう場合に局所変数が使える。

diffSquare :: Int -> Int -> Int
diffSquare a b = d * d
  where
    d = a - b

この定義の本体は、次の一行だ。

diffSquare a b = d * d

この式ではdという変数を使っている。dの定義は「where」以降にあって、「a - b」に等しいものとして定義されている。このdは、diffSquareの定義の内部でのみ使える変数だ。dの定義はdiffSquareの仮引数であるaとbに依存しているので、diffSquareの外でdを使おうとするのは無意味だし、使おうとするとエラーになる。このように、ある変数が使える範囲のことをその変数のスコープ(可視範囲)という。この例なら、dのスコープはdiffSquareの定義全体だ。ちなみに、diffSquare自体はプログラムのどこからでも使えるので、diffSquareのスコープはプログラム全体になる。

whereは予約語で、where以降の部分をwhere節という。where節にはいくつでも定義を書いてよい。複数の定義を並べるときは、do式と同じ配置規則に従う。

文法的には、where節は式ではなく定義に付属する。一つの定義に複数のwhere節を付属させることはできない。

where節中で関数定義の構文を使うこともできる。次の例は、引数の整数が2か3か5で割れるかどうかを返す関数の定義だ。

hasSmallFactor :: Int -> Bool
hasSmallFactor n = test 2 || test 3 || test 5
  where
    test k = mod n k == 0

「test k」は、nがkで割り切れるかどうかを返す。これはもちろんnに依存するので、testは局所的にしか定義できない。

定義の右辺が複雑になるとき、式を分割して読み書きしやすくするためにwhere節を使うこともできる。例をあげよう。

if式を使えば、ある年が閏年かどうか判定することができる。グレゴリオ歴x年が閏年なのは、基本的にはxが4の倍数の場合だ。ただし、例外的に、xが100の倍数なら閏年でない。さらに、これにも例外があって、xが400の倍数なら閏年だ。これを関数にしてみる。

まず、ある整数が別の整数の倍数であるかどうかを判定する関数を用意しておこう。「divisible n d」は、nがdで割り切れるかどうかを返す。

divisible :: Int -> Int -> Bool
divisible n d = mod n d == 0

これを使って、西暦で表された年が閏年かどうかを判定する関数isLeapを定義する。

isLeap :: Int -> Bool
isLeap n = divisible n 4 && (not (divisible n 100) || divisible n 400)

これは複雑で、一見して何を表しているか分かりにくい。これを次のように書き直すと、多少長くなるが、ずっと構造がはっきりするだろう。

isLeap :: Int -> Bool
isLeap n = divisible n 4 && not exn -- n年が閏年なのは、nが4の倍数で、しかも例外に当てはまらないときだ
  where -- ただし
    exn = divisible n 100 && not exn_of_exn -- 例外とは、nが100の倍数で、しかも「例外の例外」に当てはまらないときだ
    exn_of_exn = divisible n 400 -- 例外の例外とは、nが400の倍数のときだ

ガード

if式を使うと、条件に従って二つの選択肢から一つを選ぶ計算が自然に書ける。しかし、実際には三つ以上の選択肢から選びたいことも多い。例えば、100点満点の成績を受け取って、「優」「良」「可」「不可」の四段階評価を表す文字列を返す関数を書きたいとしよう。これは、if式を三つ組み合せることで以下のように書ける。

grade :: Int -> String
grade n = 
  if 80 <= n
    then "優"
    else if 70 <= n
      then "良"
      else if 60 <= n
        then "可"
        else "不可"

読み易いように複数行に分け、字下げを行なったが、それでもごちゃごちゃしていて読み書きしにくい。同じ関数を、次のように定義することができる。

grade :: Int -> String
grade n
  | 80 <= n = "優"
  | 70 <= n = "良"
  | 60 <= n = "可"
  | True = "不可"

このように、定義の左辺の部分を書いた後、「| 条件 = 本体」の形のものを複数並べることができる。条件はBool型の式なら何でもよい。この関数が適用され評価されるときは、これらの条件が上から順に吟味され、Trueであるものが見付かったら、それに対応する本体が評価される。条件が全てFalseならエラーが発生する。

これらの条件式の一つ一つをガードと呼ぶ。

この例では、最後の条件が常にTrueなので、他の条件が全てFalseだった場合には必ずこの選択肢が使われることになって、エラーは決して発生しない。このように、最後の条件をTrueにすることで、他が全て失敗した場合の選択肢を用意するのは良く使われる手法だ。こういう場合、Trueの代わりにotherwiseと書いてもいい。

-- 整数の絶対値を計算する
absolute :: Int -> Int
absolute n
  | n < 0 = negate n
  | otherwise = n

otherwiseは単なる変数で、「otherwise = True」と定義されている。otherwiseを使うことの利点は、定義が英語っぽく(あるいは数学書っぽく)読めるようになることだけだ。プログラムとしての意味は変わらないので、どちらを使うかは趣味に合わせて選べばいい。

ガードとwhere節

ガードを使った定義にwhere節を付ける場合、where節は最後に一つだけ付ければよい。この場合、where節で定義された変数は全ての選択肢から共通に使えるし、ガードの条件式からも使える。

-- 二次方程式 ax^2 + bx + c = 0 の解の個数を計算する
nRoot :: Int -> Int -> Int -> Int
nRoot a b c
  | det > 0 = 2
  | det == 0 = 1
  | otherwise = 0
  where
    det = b * b - 4 * a * c

リスト

プログラミングでは、データの「並び」をひとまとめにして扱いたいことがよくある。例えば、一時間ごとに観測された気温のデータがあるとして、そこから最高気温、最低気温、平均気温を算出したいなら、観測データの並びを使って計算するのが楽だ。このような「並び」を表現する構造のうち、Haskellでもっとも良く使われるのはリストというデータ構造だ。この節ではリストについて解説する。

リストの表記

リストは、式をコンマで区切り、全体を大括弧で括って書き表す。以下はリストの例だ。

Prelude> [1, 2, 3, 2]
[1,2,3,2]
Prelude> [1 + 2, 3 + 4]
[3,7]
Prelude> [False, False, False, True]
[False,False,False,True]

リストの中身をそのリストの要素という。例えば2は[1,2]の要素だ。また、あるリストに含まれる要素の数のことを、そのリストの長さという。例えば[1,2,2]の長さは3だ。次のように、長さが1や0のリストもある。

Prelude> [127]
[127]
Prelude> []
[]

あるリストについて、その要素の型は全て同じでないといけない。だから、例えば["year", True, 8]のようなリストは存在できない。要素の型がTのとき、そのリストの型は[T]と書く。だから[True, False]の型は[Bool]だ。

リスト操作の例/データ集計

リストを操作するプログラムの例として、データ(たとえば気温)の列が書かれたファイルを読んで、その平均値を出力するプログラムを書くことを考えよう。単に平均を計算するだけなので、入力データは気温でも湿度でも、ウェブサイトの訪問者数でも構わない。

入力データは、カレントディレクトリにinput.txtという名前で用意することにする。内容は、整数データを、十進表記で、一行に一つずつ書くとしよう。つまり、例えば次のようなファイルになる。

3
1
-1
-3
-2

この状態でプログラムを実行すると-1が表示される(平均は-0.4、小数部を切り捨てて-1)ようにしたい。

平均を計算する関数

このプログラムの肝は整数の列の平均を取るところにあるので、その機能を関数として定義することを考えよう。ファイルからどうやって整数の列を取り出すか、とか、計算した平均をどうやって表示するか、といった問題は後回しにする。

整数のリストから平均を計算するには、要素の合計を要素数で割ればいい。要素数(リストの長さ)は関数lengthで得られる。

Prelude> length [2,3,4]
3
Prelude> length []
0

また、要素の合計は関数sumで得られる。

Prelude> sum [2,3,4]
9
Prelude> sum []
0

だから、整数のリストからその平均値への関数averageは次のように定義できる。

average :: [Int] -> Int
average xs = div (sum xs) (length xs)

試してみよう。

*Main> average [2, 5, 9]
5
*Main> average [-1, -2]
-2
*Main> average [99]
99

averageの引数が長さ零のリスト(空リストという)なら、エラーが発生する。

*Main> average []
*** Exception: divide by zero

入出力を書く

テキストファイルの中身を読むには、readFileという関数を使う。

readFile :: String -> IO String

readFileの引数としてファイル名を指定すると、そのファイルを読むコマンドが返る。このコマンドが実行されると、ファイルの内容全体を含んだ文字列が結果になる。例えば、上の例のようなinput.txtがカレントディレクトリに存在する状態で「readFile "input.txt"」を実行すると、結果は"3\n1\n-1\n-3\n-2\n"という文字列になる。

結果の表示にはputStrLnを使えばいいだろう。だから、mainの定義は次のような形になる。

main :: IO ()
main = do
  content <- readFile "input.txt"
  putStrLn (process content)

ただし、processはこれから定義する関数で、ファイルの内容を受け取って、結果を文字列で返す。例えば「process "3\n1\n-1\n-3\n-2\n"」が「"-1"」に評価されるようにしたい。processの定義を考えよう。

既に見たように、ファイルの内容は"3\n1\n-1\n-3\n-2\n"のような一塊の文字列として与えられる。一方、averageは[3, 1, -1, -3, -2]のような整数のリストを要求するから、何とかして前者を後者に変換しないといけない。最初のステップは関数linesを使うことだ。これは文字列を行ごとに区切り、行のリストを作る。例を見てみよう。

Prelude> lines "abc\ndef\n"
["abc", "def"]
Prelude> lines "stream and\npipelining"
["stream and", "pipelining"]
Prelude> lines "3\n1\n-1\n-3\n-2\n"
["3", "1", "-1", "-3", "-2"]

これで["3", "1", "-1", "-3", "-2"]のような文字列のリストが手に入るので、これの各要素を整数に変換したものを計算すればいい。文字列を整数に変換するにはreadを使うのだった。したがって、入力リストの各要素にreadを適用して、その結果からなるリストを作ればいいことになる。このように、リストの全ての要素を同じ関数で変換するにはmap関数を使う。

*Main> map negate [1, 2, -3]
[-1,-2,3]
*Main> map show [1, 2, -3]
["1","2","-3"]
*Main> map length ["foo", "bar", "baz", "quux"]
[3,3,3,4]
*Main> map readInt ["3", "1", "-1", "-3", "-2"]
[3,1,-1,-3,-2]

以上を組み合せると、processは次のように定義できる。

process :: String -> String
process input = show (average (map read (lines input)))

リスト操作の例/空行の削除

もう一つリスト操作をするプログラムの例をみておこう。input.txtというテキストファイルがあったとして、その内容から空行を全て除いたものを標準出力に書き出すプログラムを考える。例えばinput.txtの内容が以下のようになっていたとしよう。

first line

second line

この場合、出力は次のようになってほしい。

first line
second line

リンク

公式

処理系

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