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をおすすめできる。

処理系を導入する

入れる処理系を選ぶ

2011年6月の時点では、以下の処理系・パッケージがメンテナンスされている。

Haskell Platform
GHCと一緒にいくつかのツールとライブラリをまとめてインストールできるパッケージ。おすすめ。
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

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

UNIX互換OS

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という名前にするなら、day.hsに以下の二行を追加する。

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

二行目はいつもどおりの変数定義だ。一行目で「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での文字列の扱いについて基本的なところを説明する。

文字列リテラル

前に紹介したように、文字列とは文字が0個以上並んだものだ。例えば、"図4α"は「図」「4」「α」の三つの文字からなる文字列であり、""は零個の文字からなる文字列(空文字列という)だ。

Haskellでいう文字とは、Unicodeコードポイントのことだ。厳密な解説はweb上の資料を参照してほしいが、Unicodeとは、おおざっぱに、ラテンアルファベット、平仮名、片仮名、漢字、アラビア文字、ハングル、キリル文字など、世界中で使われているあらゆる文字と、アラビア数字や、普通にPCで扱えるあらゆる記号を集めて、全てに番号を振ったものだ。例えばラテン文字の「a」は97、漢字の「鰯」は39983、キリル文字の「в」は1074になる。このように、文字に対応する番号をコードポイントという。

このような目に見える文字の他に、特殊な効果を持つ文字群があって、制御文字と呼ばれる。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.」とだけ出力するようにせよ。

便利な構文

関数定義

実際の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

続・コマンド

この節では、コマンドやdo式について、まだ説明していなかった事柄について扱う。

do式の中でif式を使う

(TODO: 書く)

return

returnは「なにもしないで、結果だけ生成するコマンド」を作る関数だ。例えば、「return 1」は、「なにもせず、1を結果とする」コマンドになる。

returnを使うと、自分で作ったコマンドがどんな値を結果とするかを自由に選べるようになる。例えば、「標準入力から一行読み、それを捨て、常に1を結果とする」コマンドは次のように定義できる。

foo :: IO Int
foo = do
  getLine
  return 1

もうすこし実用的な例として、ユーザにyes/noの質問をする関数askを考えてみる。例えば「ask "Do you like logic?"」のようにして使うと、「標準出力に「Do you like logic?」と表示し、標準入力から一行読み込み、それが"yes"ならTrue、そうでなければFalseを結果とする」コマンドになるようにしたい。このaskは次のように定義できる。

ask :: String -> IO Bool
ask question = do
  putStrLn question
  ln <- getLine
  return (ln == "yes")

==演算子は、整数だけでなく様々な値の等しさを判定できる。ここでは文字列を比較している。

こうして定義したaskは、例えば次のようにして使う。

main :: IO ()
main = do
  a1 <- ask "Do you like cheese?"
  a2 <- ask "Do you like cake?"
  putStrLn
    (if a1 && a2
      then "Then you like cheesecake!"
      else "I have nothing to say about you.")

let文

do式を書いていると、do式内部でのみ使える変数を定義したくなることがある。次の例は、入力された整数の一の位と十の位の和を表示するプログラムだ。

main :: IO ()
main = do
  input <- getLine
  putStrLn (show (dig1 (read input)) ++ " + " ++ show (dig10 (read input)) ++ " = " ++ show (dig1 (read input) + dig10 (read input)))

-- 一の位
dig1 :: Int -> Int
dig1 x = mod x 10

-- 十の位
dig10 :: Int -> Int
dig10 x = dig1 (div x 10)

このプログラムを実行して「1234」と入力すれば、「4 + 3 = 7」と出力される。このプログラムは正しいが、結果を表示するところで同じ式を何度も書いていてスマートでない。

let文を使うと、このmainの定義を次のように書き直せる。

main :: IO ()
main = do
  input <- getLine
  let num = read input
  let d1 = dig1 num
  let d10 = dig10 num
  putStrLn (show d1 ++ " + " ++ show d10 ++ " = " ++ show (d1 + d10))

このように、do式中に「let 変数 = 式」という形のものを書くことで、局所的な変数を定義できる。このようにして定義した変数は、このdo式のうち、定義された場所以降の部分で使える。letは予約語だ。

なお、とは、do式中に並べることができるもののことだ。let文の他に、「変数 <- 式」という形のものと、単なる式(例えば「putStrLn "hello"」)の二つがある。前者を束縛文、後者を式文と言ったりする。

一つのlet文で複数の変数を定義することもできる。この場合、let文の内容は配置規則に従って並べる。例えば、上の例は次のように書き換えても同じだ。

main :: IO ()
main = do
  input <- getLine
  let 
    num = read input
    d1 = dig1 num
    d10 = dig10 num
  putStrLn (show d1 ++ " + " ++ show d10 ++ " = " ++ show (d1 + d10))

このdo式は三つの文から成り立っている。上から順に、束縛文、let文、式文だ。

リスト

プログラミングでは、データの「並び」をひとまとめにして扱いたいことがよくある。例えば、一時間ごとに観測された気温のデータがあるとして、そこから最高気温、最低気温、平均気温を算出したいなら、観測データの並びを使って計算するのが楽だ。このような「並び」を表現する構造のうち、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 show [1, 2, -3]
["1","2","-3"]
*Main> map length ["foo", "bar", "baz", "quux"]
[3,3,3,4]
*Main> map (\x -> x + 1) [1, 2, -3]
[2,3,-2]
*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

データの流れ

ファイルの内容を読み込んで、それをlinesで行単位に分割するところまでは前節のプログラムと同様に実現できる。さらに、関数unlinesを使うと、このように分割された行のリストを一つの文字列に戻すことができる。

Prelude> unlines ["ab", "c", "", "de"]
"ab\nc\n\nde\n"

unlinesはlinesのほぼ逆*3の操作になっている。だから、次のような流れでファイルから空行を取り除くことができる。

  1. linesで入力を行ごとに分割したリストを得る。入力文字列が"first line\n\nsecond line\n"なら、["first line", "", "second line"]が得られる。
  2. この行のリストから、空文字列を除いたものを得る。この例なら["first line", "second line"]が得られる。
  3. これにunlinesを適用して、結果を得る。これは"first line\nsecond line\n"になる。

mainは次のように定義できる。ただし、removeEmpties :: [String] -> [String]は、これから定義する関数で、文字列のリストから空文字列を全て除いたものを返すとする。

main :: IO ()
main = do
  input <- readFile "input.txt"
  putStr (unlines (removeEmpties (lines input)))

putStrは、putStrLnと似ているが、文字列を出力するだけで、改行しない点が異なる。

filter

removeEmptiesを定義することを考えよう。このように、リストから特定の条件を満たす要素のみを抽出したリストを得るには、filterという関数を使う。filterは二引数の関数で、「filter 条件 リスト」のように使う。結果は、「リスト」の要素のうち「条件」を満たすものだけからなる新しいリストになる。

Prelude> filter even [1, 2, 3, 4, 99]
[2,4]
Prelude> filter odd [1, 2, 3, 4, 99]
[1,3,99]

ここで、evenは「偶数である」という条件、oddは「奇数である」という条件だ。

evenやoddの正体は、単に「真偽値を返す関数」だ。

Prelude> even 2
True
Prelude> even 3
False
Prelude> even 4
True

このように、「偶数であればTrue、そうでなければFalseを返す関数」で、「偶数である」という条件を表現している。同じように、真偽値を返す関数を自分で作れば、それを「条件」としてfilterの第一引数に指定できる。

Prelude> (\x -> x > 10) 5
False
Prelude> (\x -> x > 10) 10
False
Prelude> (\x -> x > 10) 12
True
Prelude> filter (\x -> x > 10) [9, 10, 12, 24]
[12,24]

removeEmptiesを定義する問題に戻ろう。removeEmptiesは、元のリストから空文字列を全て削除したものを返す。言い換えると、「空文字列でない」という条件を満たす要素だけからなるリストを作ればよい。また、「空文字列でない」という条件は、「長さが0でない」と言い換えられるから、removeEmptiesは次のように定義できる。

removeEmpties :: [String] -> [String]
removeEmpties ls = filter (\x -> length x /= 0) ls

試してみる。

*Main> removeEmpties ["a", "^^", "", " ", "#########", ""]
["a","^^"," ","#########"]

さらにリスト

この節では、リスト操作関数のうち、ここまでに紹介できなかったものについて解説する。

(++)とconcat

++演算子は、二つのリストを連結したリストを返す。

Prelude> [True, False] ++ [False]
[True,False,False]

concatはリストのリストを受けとり、その要素を全て連結したリストを返す。

Prelude> concat [[1, 2], [], [3], [4, 5, 6]]
[1,2,3,4,5,6]
Prelude> concat [[[1], []], [[2,3]]]
[[1],[],[2,3]]

reverse

reverse xはxを逆順にしたリストになる。

Prelude> reverse [1,2,3,4]
[4,3,2,1]
Prelude> reverse [[1,2], [3,4]]
[[3,4],[1,2]]

replicate

replicate n xはn個のxを要素とするリストを作る。

Prelude> replicate 5 7
[7,7,7,7,7]
Prelude> replicate 3 True
[True,True,True]

数列表記

[1,2,3,4,5]のような連続する整数のリストは、専用の記法で生成できる。

Prelude> [1..5]
[1,2,3,4,5]
Prelude> [2..2]
[2]
Prelude> [2..1]
[]

文字

文字のリスト

lengthや(++)といった関数が文字列にもリストにも使えることに気づいた読者もいるかもしれない。これは、文字列が一種のリスト、具体的には文字のリストだからだ。

Haskellで文字を表すには、その文字を一重引用符で括ればよい。例えば'a'は「a」という文字を表すリテラルだ。文字列リテラルの場合と同じく、エスケープコードを使うこともできる。'\n'は改行文字だ。

文字の型はCharと書く。文字列は文字のリストだから、Stringと[Char]は同じ型を表す。もっと正確にいうと、Stringは[Char]という型に付けられた別名にすぎない。

同様に、例えば"abc"と['a', 'b', 'c']は同じ値を表している。前者が後者の略記法だと考えてもいい。

Prelude> ['f', 'u', 'l', 'l']
"full"

文字列は文字のリストなので、リスト操作関数の多くは文字列に対しても使うことができる。

Prelude> concat ["foo", show (1+1), "bar"]
"foo2bar"
Prelude> reverse "Dog"
"goD"

型シノニム

Stringは[Char]の別名だが、このような型に対する別名(型シノニムという)を自分で付けることもできる。これには次のようなtype宣言を使う。

type String = [Char]

次の例では、整数から真偽値への関数に「IntPred」という別名を付け、実際に使っている。

-- 整数から真偽値への関数、あるいは整数の「性質」
type IntPred = Int -> Bool

-- 二桁の整数であるという性質
doubleDigit :: IntPred
doubleDigit x = 10 <= x && x < 100

-- 二つの性質を両方満たすという性質
andp :: IntPred -> IntPred -> IntPred
andp p q = \x -> p x && q x

-- 1から100までの整数で、与えられた性質を満たすものがいくつあるか調べる
count100 :: IntPred -> Int
count100 f = length (filter f [1..100])

main :: IO ()
main = do
  putStrLn ("count100 doubleDigit = " ++ show (count100 doubleDigit)) -- 二桁の数はいくつあるか?
  putStrLn ("count100 even = " ++ show (count100 even)) -- 偶数はいくつあるか?
  putStrLn ("count100 (andp even doubleDigit) = " ++ show (count100 (andp even doubleDigit))) -- 二桁の偶数はいくつあるか?

ここまで、型についてはいい加減な記述で逃げてたが、この節で詳しく説明する。

静的な型付け

全ての値には型がある。例えば"redone"の型はStringだし[True]の型は[Bool]だ。コロン二つの記法を使えば「"redone" :: String」とか「[True] :: [Bool]」とか書ける。さらに、全ての式にも型がある。「1 < 2 :: Bool」や「reverse "redone" :: String」という具合だ。

式の型を計算することを型付けという。型付けは失敗することもある。例えば、「not "鯖"」という式は、Bool型であるべきnotの引数として、型Stringの式が指定されているので、型付けできない。この例では、型付けができないのは式が間違っている("鯖"を否定するって何?)からだ。このように、型付けによってプログラムの誤りが判明することがあるので、型付けは型検査ともいう。

Haskellでは、プログラムの実行が始まるより前に、プログラム全体が型検査される。これを静的な型付けといい、「Haskellは静的な型付けをする言語だ」などと使う。「静的」というのは「実行が始まるより前」という意味だ。

これは、ghcコマンドでプログラムをコンパイルする場合が分かりやすい。次に示すのは型付けできない(型エラーがある)プログラムの例だ。

main :: IO ()
main = putStrLn (if not "鯖" then "2" else "1")

これを、例えばtypeerror.hsという名前を付けて保存し、ghcコマンドでコンパイルを試みる。すると、以下のようにエラーが発生する。

$ ghc --make typeerror.hs
[1 of 1] Compiling Main             ( typeerror.hs, typeerror.o )

typeerror.hs:2:24:
    Couldn't match expected type `Bool' against inferred type `[Char]'
    In the first argument of `not', namely `"\39894"'
    In the predicate expression: not "\39894"
    In the first argument of `putStrLn', namely
        `(if not "\39894" then "2" else "1")'

このように、コンパイルの段階で型検査が行われ、エラーがあった場合はそもそも実行ファイルが生成されない。GHCiに式を入力した場合も同じ流れで、まず式全体が型検査されて、エラーがなかった場合のみ評価が開始される。

一方、整数を0で割ろうとしたり、ガードを使った定義で条件を満たす選択肢が無かったりした場合のエラーは、プログラムの実行が始まった後、その式が実際に評価された時点で発生する。このようなエラーを実行時エラーと呼ぶ。次は、実行時エラーを発生させるプログラムの例だ。

main :: IO ()
main = putStrLn (show (div 1 0))

これをghcコマンドでコンパイルすると、コンパイルは成功して、実行ファイルが生成される。できたファイルを実行すると、その時点で0除算のエラーが報告される。

静的型付けによる制限

Haskellでは、評価が始まる前に式が型検査されるので、式の評価結果と無関係に型が定まらないといけない。例えば、次の定義はエラーになる。

v :: Int
v = if 1 > 0 then 1 else "hell"

このif式の条件は必ず満たされるが、それは「1 > 0」を評価しないと分からない。だから、型検査の段階ではthen部かelse部のどちらが選ばれるか判断できない。

この理由から、if式のthen部とelse部は同じ型の式でないといけない。

多相型

lengthの型について考えてみる。lengthは一引数の関数で、結果の型はIntだから、「何か -> Int」という型だと想像がつく。しかし、lengthの引数の型は一つに決まらない。lengthには、整数のリストも真偽値のリストも渡せるし、一般にリストならなんでも渡せる。

Prelude> length [3, 3, 1]
3
Prelude> length [True, True, True]
3
Prelude> length [length, length]
2

GHCiには:type(省略形は:t)というコマンドがあって、式の型を表示できる。

Prelude> :t "ray"
"ray" :: [Char]
Prelude> :t 1 > 2
1 > 2 :: Bool

これを使ってlengthの型を調べてみると、以下のようになる。

Prelude> :t length
length :: [a] -> Int

「a」の部分は型変数といって、その部分を好きな型に置き換えてよいということを示している。例えば、aをIntに置き換えると、この型は[Int] -> Intになるので、lengthを「[Int] -> Int」型の値として扱うことができる。同じように、[Bool] -> Intとしても[[Int]] -> Intとしても扱えることが分かる。

型変数の名前はなんでもいい。例えば「length :: [b] -> Int」や「length :: [elementType] -> Int」と書いても全く同じ意味になる。ただし、型変数の名前は小文字で始まっていないといけない。大文字で始まる名前は型の名前だと解釈される。

型変数を含む型を多相型という。多相型を持つ値を多相的であるといい、「lengthは多相的な関数だ」などと使う。

もう少し複雑な例を見てみよう。replicateの型は以下のとおり。

replicate :: Int -> a -> [a]

この型では型変数aが二回使われている。この場合もaは好きな型に置き換えてよいが、二箇所のaは同じ型に置き換えないといけない。例をあげると、replicateは「Int -> Bool -> [Bool]」という型の値としては使えるが、「Int -> Bool -> [String]」としては使えない。

複数の型変数を含む型もある。mapの型がその例だ。

map :: (a -> b) -> [a] -> [b]

例えば、map show [1, 2]という使い方では、aがIntに、bがStringに対応する。確認してほしい。

多相的な変数の定義

自分で多相的な変数を定義することができる。特別な記法は必要なく、通常の変数定義に多相型を指定するだけだ。例えば、リストを受け取って、それを逆順にしたものを元のリストに連結したものを返す関数mirrorは次のように定義できる。

mirror :: [a] -> [a]
mirror xs = xs ++ reverse xs

この関数は次のように使える。

*Main> mirror [1]
[1,1]
*Main> mirror "te"
"teet"

必要なら、より特殊な(一般的でない)型を指定することもできる。例えば、mirrorがIntのリストだけを扱うように制限したいなら、定義本体はそのままで、型シグネチャを以下のように書き換えればいい。

mirror :: [Int] -> [Int]

型推論

これまで、局所的でない定義(最上位の定義という)では、常に型シグネチャを書いてきた。また、局所的な定義には型シグネチャを書かなかった。

rjust :: Int -> String -> String -- ←これが型シグネチャ
rjust n s = spaces ++ s
  where
    -- ここにspacesの型シグネチャがない
    spaces = replicate (n - length s) ' '

実際には、特殊な状況を除いて、型シグネチャは書いても書かなくてもいい。ある変数の定義で型シグネチャを書かなかった場合、その変数の型は自動的に推論される。

最上位の変数については、推論の結果をGHCiから確認できる。次のように、型シグネチャを使わずに定義したとしよう。

mirror xs = xs ++ reverse xs

これをGHCiにロードし、:tコマンドで型を確認する。

*Main> :t mirror
mirror :: [a] -> [a]

正しい型が推論されている。

このように、型シグネチャを書くことは必須でないので、型シグネチャを書くかどうか、書くとしてどの程度書くか、というのはスタイルの問題になる。型シグネチャを書くことの利点は、だいたい次の二つだ。

この文書では、原則として、最上位の定義には型シグネチャを付け、局所定義には付けないというスタイルを採る。

型クラス

制約

前に紹介したように、==演算子を使うと整数と整数を比較できる。

Prelude> 1 == 1
True
Prelude> 1 == 1 + 1
False

==演算子で比較できるのは実は整数だけではない。次のように、Bool、Char、String、()など、多くの型の値を比較できる。ただし、左辺と右辺で型が同じでないといけない。

Prelude> True == False
False
Prelude> '\10' == '\n'
True
Prelude> "tanasin" == "tanasinn"
False
Prelude> () == ()
True
Prelude> "f" == 'f'

<interactive>:1:7:
    Couldn't match expected type `[Char]' against inferred type `Char'
    In the second argument of `(==)', namely 'f'
    In the expression: "f" == 'f'
    In the definition of `it': it = "f" == 'f'

一方、比較できない型もある。関数やIOコマンドがその一例だ。

Prelude> length == length

<interactive>:1:0:
    No instance for (Eq ([a] -> Int))
      arising from a use of `==' at <interactive>:1:0-15
    Possible fix: add an instance declaration for (Eq ([a] -> Int))
    In the expression: length == length
    In the definition of `it': it = length == length
Prelude> putStrLn "hello" == putStrLn "hello"

<interactive>:1:0:
    No instance for (Eq (IO ()))
      arising from a use of `==' at <interactive>:1:0-35
    Possible fix: add an instance declaration for (Eq (IO ()))
    In the expression: putStrLn "hello" == putStrLn "hello"
    In the definition of `it':
        it = putStrLn "hello" == putStrLn "hello"

つまり、(==)は「a -> a -> Bool」という型を持つように振る舞うが、型変数aに入る型に一定の制約がある。

(==)の型は次のように書く。

(==) :: Eq a => a -> a -> Bool

「Eq a」の部分が制約を表している。これは「型変数aは、Eqという性質を持った型でなければならない」という意味だ。Eqはequalityの略で、等値比較ができるという性質を表す。Eqのように、型の性質を表すものを型クラス(または略してクラス)といい、「Eq a」のように型クラスと型変数を並べたものを制約という。制約は括弧で囲んでも囲まなくてもいい。

「等値比較ができる」という性質を持つ型は具体的にはInt、Bool、Stringなどだ。このように、ある型クラスの表す性質を満たす個々の型をその型クラスのインスタンスという。例えば、IntやBoolはEqのインスタンスで、Int -> IntやIO ()はEqのインスタンスでない。

クラスの名前は大文字で始まる。

以下では、普段のプログラミングで頻繁に使うことになるだろう型クラスをいくつか紹介する。

Eq

Eqは、(==)や(/=)で比較できる型を表す。Int、Bool、Charといった基本的なデータは全てEqのインスタンスだ。リストについては、要素の型TがEqのインスタンスであるときに限り、[T]もEqのインスタンスになる。だから、例えばStringや[Int]や[[Int]]はEqのインスタンスだが、[Int -> Int]は違う。

Eq制約を持つ関数の例としてelemがある。elemは、リストの中に指定された値が存在するかどうかを判定する。

Prelude> :t elem
elem :: (Eq a) => a -> [a] -> Bool
Prelude> elem  3 [2,4,6]
False
Prelude> elem ' ' "second p"
True

Ord

Eqが「等しいかどうか」の判定を許す型のクラスであるのに対して、Ordは大小の比較ができることを保証する。(<)、(<=)、(>)、(>=)の四つの比較関数は全て「(Ord a) => a -> a -> Bool」という型を持つ。Ordのインスタンスは必ずEqのインスタンスになっている。

Int、Bool、CharはOrdのインスタンスだ。Charについては、Unicodeコードポイントの大小順が使われる。例えば、「a」のコードポイントは97、「鰤」のコードポイントは39972なので、'a'より'鰤'の方が大きいと扱われる。

Eqの場合と同じく、リストは、要素の型がOrdのインスタンスであるときに限りOrdのインスタンスになる。リストの大小比較は辞書順で行われる。

maxとminという関数は、二つの値から大きい方または小さい方を得るものだ。

Prelude> :t max
max :: (Ord a) => a -> a -> a
Prelude> max 4 5
5
Prelude> min "tree" "root"
"root"

一つのリストの要素のなかで最大のものと最小のものを得るには、maximumおよびminimumという関数を使う。

Prelude> :t maximum
maximum :: (Ord a) => [a] -> a
Prelude> maximum [10, 3, 7]
10
Prelude> minimum (map (\x -> x * (x - 7)) [1..100])
-12
Prelude> maximum []
*** Exception: Prelude.maximum: empty list

Ordの典型的な利用法として、リストを並べ換えるというのがある。sortという関数を使うと、リストを昇順に並べ換えたリストが得られる。

ソースファイル中でsortを使うには、ファイルの先頭に次の一行を書いておく必要がある。

import List

これをimport宣言という。これが必要なのは、sortがListというモジュールに属する変数だからだ。モジュールとは変数や型の定義をひとまとめにしたもので、Listモジュールにはリストを操作するための関数がまとめられている。このimport宣言は、Listモジュールで定義された変数を使えるようにするものだ。

GHCiでは、「import List」または「:module +List」と入力すれば、それ以降Listモジュールの定義を使えるようになる。

Prelude> :t sort

<interactive>:1:0: Not in scope: `sort'
Prelude> import List
Prelude List> :t sort
sort :: (Ord a) => [a] -> [a]

これで、sort関数を試してみることができる。

Prelude List> sort [3,8,7,-1,99]
[-1,3,7,8,99]
Prelude List> sort ["az", "a", "ba", "bz", "b"]
["a","az","b","ba","bz"]

なお、lengthや(+)などの変数、IntやStringなどの型はimport宣言なしで使えるが、これらはPreludeというモジュールの一員だ。Preludeは特別なモジュールで、import宣言を書かなくても暗黙にインポートされる。

ShowとRead

showとreadも整数以外の多くの型に対して使える。対応するクラスはそれぞれShowとReadだ。

show :: (Show a) => a -> String
read :: (Read a) => String -> a
Prelude> show 1
"1"
Prelude> show True
"True"
Prelude> show otherwise
"True"
Prelude> show [2, 3, 5]
"[2,3,5]"
Prelude> show 'c'
"'c'"
Prelude> show "string"
"\"string\""

実は、GHCiは(IOコマンド以外の)評価結果を表示する際にこのshow関数を利用している。関数を表示しようとするとエラーになるのは、関数の型がShowのインスタンスでないからだ。

Showクラスの利用例としてprint関数がある。printは、引数を標準出力に表示して改行する。これは要するに、引数にshowを適用した結果にputStrLnを適用しているだけだが、場合によっては便利だ。

print :: (Show a) => a -> IO ()

文脈

一つの型シグネチャに複数の制約が必要なこともある。複数の制約はコンマで区切って並べる。この場合、制約全体を囲む括弧は必須になる。

makeList :: (Show a, Ord a) => [a] -> String
makeList xs = unlines (map show (sort xs))

このような制約の並び(この例では「(Show a, Ord a)」の部分)を文脈と呼ぶ。

クラス制約の曖昧さ

多相的な変数のうち、型にクラス制約を含むもの(show、max、sortなど)は多重定義された変数と呼ばれる。これらは型変数にどんな型が代入されるかによって異なる振る舞いをするからだ。たとえば、sort関数は、Intのリストに対して適用されれば単純な大小順の並べ換えをするのに対し、Stringのリストに適用された場合には辞書順による並べ換えになる。これに対して、制約を含まない多相的な変数(lengthなど)は、どんな型として使われても同じ振る舞いをする。

このため、多重定義された変数については、その変数が具体的にどの型の値として使われるかが一つに決まらないといけない。そうでないと、曖昧エラーが発生する。例えば次の関数定義はエラーになる。

reshow :: String -> String
reshow x = show (read x)

この場合、readの結果の型とshowの引数の型が決まらないので、これらの関数が具体的に何をすれば良いのかも確定しない。GHCi上で「read "1"」などを評価しようとした場合に発生するエラーも同じ原因だ。GHCiは入力された式の結果をshowで文字列にしようとするが、結果の型が確定していないので曖昧になる。

このような場合、式の型を明示することで曖昧さを取り除くことができる。例えば次のようにする。

reshow :: String -> String
reshow x = show (read x :: Int)

式の型を明示するには「式 :: 型」という形の式を使う。定義に使う型シグネチャと似た構文で、式型シグネチャという。ここでの「::」は演算子ではないが、文法的には優先順位の最も低い演算子のように振る舞う。だから、「x + y :: T」は「x + (y :: T)」でなく「(x + y) :: T」と同じ意味になる。

なお、このように曖昧エラーになるのは、多重定義された変数の型が決まらず、将来的にも決まる見込みのない場合だけだ。次のようなものは問題ない。

decorate :: (Show a) => a -> String
decorate v = "<" ++ show v ++ ">"

この関数定義では、showの引数であるvの型がまだ確定していないが、decorateの使われ方が決まればvの型も決まるので、曖昧さはない。

タプル

何それ

(TODO:タプルが欲しくなる例を書く)

タプルは複数の値をまとめたものだ。タプルは、値をコンマで区切って、全体を括弧で囲んで書き表す。

Prelude> (1, True)
(1,True)
Prelude> (3 + 4, 3 < 4)
(7, False)
Prelude> ("z", True, "foo")
("z",True,"foo")

タプルの型は、要素の型をコンマで区切って、全体を括弧で囲んで書き表す。例えば、("z", True)の型は(String, Bool)になる。

パターン照合

タプルが与えられたとき、その要素の値を取り出すにはパターン照合を使う。まず、次に示すのは通常の変数定義だ。

v :: (Int, String)
v = (4, "Rich")

このように定義すると、vは(4, "Rich")に束縛される。

次に、これを以下のように書き換える。

a :: Int
b :: String
(a, b) = (4, "Rich")

こうすると、aは4に、bは"Rich"に束縛される。このように、定義の左辺には単純な変数以外のものを書くことができて、それをパターンと呼ぶ。この例では「(a, b)」がパターンだ。パターン中に出現した変数は、定義の右辺の式の値の中の、対応する部分に束縛される。「(a, b)」というパターンなら、aは第一要素に、bは第二要素に束縛されることになる。

変数を束縛できる構文の多くでは、変数の代わりにパターンを指定できる。例えば、Int三つからなるタプルを受け取って、その要素の総和を返す関数は、where節でパターンを利用すれば次のように書ける。

sum3 :: (Int, Int, Int) -> Int
sum3 tup = x + y + z
  where
    (x, y, z) = tup

関数の仮引数としてパターンを直接書いてもいい。sum3は次のようにも書ける。

sum3 :: (Int, Int, Int) -> Int
sum3 (x, y, z) = x + y + z

fstとsnd

このように、タプルの内容はパターン照合で取り出すのが普通だが、二要素のタプル(ともいう)に限っては専用の関数が用意されているので、こっちを使ってもいい。

fst :: (a, b) -> a
snd :: (a, b) -> b

fstは対の第一要素、sndは第二要素を返す。

Prelude> fst (True, 44)
True
Prelude> snd (True, 44)
44

リンク

公式

処理系

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

その他

The Haskell 98 Report (Revised)
Haskell 98の言語仕様
Foreign Function Interface
他言語との相互呼び出しを可能にするHaskell 98への拡張
Hierarchical modules
階層的モジュール(Haskell 98への拡張)
The Glorious Glasgow Haskell Compilation System User's Guide
GHCのマニュアル
Haskell Hierarchical Libraries
GHC付属ライブラリと準付属ライブラリのリファレンス
HackageDB
公開されているHaskellライブラリとプログラムの集積所
Hoogle
ライブラリ関数を名前や型から検索できるサービス

日本語サイト

Programming in Haskell
Haskellに関する日本語の情報集
Haskell 98 言語とライブラリ 改訂レポート
Haskell 98の言語仕様の日本語訳
栄光のグラスゴーHaskellコンパイルシステム利用の手引き
GHCのマニュアルの日本語訳

*1 ただし、GHC 6.8.2の時点ではputStrLnでそのまま日本語を表示できない。日本語の扱いについては後述するつもり。
*2 関数の「結果」と同じ用語だが、違うものなので注意。
*3 元の文字列xが空でなく、しかも改行で終っていない場合、unlines (lines x)はxに戻らず、xの末尾に改行を一つ追加した文字列になる。面倒なので、ここでは「改行で終わらないファイルを作る方が悪い」という立場を取り、この問題を無視する。

トップ   編集 凍結 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2023-02-23 (木) 23:33:35