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.6.1

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

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」でも良い。起動すると次のような画面が出てくるはずだ。

   ___         ___ _
  / _ \ /\  /\/ __(_)
 / /_\// /_/ / /  | |      GHC Interactive, version 6.6.1, for Haskell 98.
/ /_\\/ __  / /___| |      http://www.haskell.org/ghc/
\____/\/ /_/\____/|_|      Type :? for help.

Loading package base ... linking ... done.
Prelude>

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

Prelude> 33 + 34
67
Prelude> 

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

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

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

演算子と関数

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

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

Prelude> 33 - 34
-1

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

Prelude> 3 + 6 - 1 - 4
4

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

Prelude> 3+6        - 1-  4
4

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

Prelude> 7
7

掛け算は「*」で表す。

Prelude> 2 * 7
14

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

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

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

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

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

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

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

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

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

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

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

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

エラー

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

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

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

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

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

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

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

Prelude> div div 34 3 2

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

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

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

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

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

定義

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

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

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

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

Prelude> 14 * 86400
1209600

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

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

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

day :: Int
day = 86400

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

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

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

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

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

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

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

day :: Int
day = 86400

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

リンク

公式

処理系

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

日本語サイト

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


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