なぜ Haskell を使うのか?

date:2012/10/19

このページは私が個人的に思ったことを書いていくため、時々内容が変わります。 注意点としては、私は他の関数型言語どころか、他のどの言語についてもほとんど知りません。


1. 色々書く前に...

プログラミング言語には、どんな種類があるでしょうか。調べてみました。 分類の基準については、 まつもとゆきひろの「プログラミング言語論」[前編](3):IT Pro を参考にしています。

1.1 分類の基準

ここに述べるものはあくまで”分けるとすれば”程度のもので、明確にわけられるわけではありません。あくまでおおざっぱなものです。

  • 低級言語か, 高級言語か:
    - 低級言語 : マシン語とアセンブリ
    - 高級言語 : 低級言語以外
  • スタイル: 明確に分けられない
    - 手続き型 : 実行すべき手順そのものを書く。
    - 関数型 : (手続き型言語の意味でない、数学の意味の)関数を書く。
    - 論理型 : 述語論理。Predicate, Fact, Rule, query などを書く。
    - オブジェクト指向 : オブジェクト(データと手続きを組みにしたもの)のメッセージによって全体を書く。
  • 型付け: ここでは強弱は忘れる
    - 型がない : 型なんてなかった。
    - 動的型付け : 実行時に決めよう。
    - 静的型付け : コンパイル時点で型が決まっているべき。

1.2 分類の例

Wikipedia, 言語ごとのサイトの説明, 伝聞などから作りました。
スタイルについては、勝手に決めたものがあります。(バージョンに依存しているもの, マルチパラダイムな言語など。)
また、型の強弱、関数型でも純粋かどうかなどは、ここでは忘れます。
型付け:スタイル 手続き型 関数型 論理型 オブジェクト指向
型がない   Lazy K,Unlambda    
動的型付け BASIC(?) LISP,Scheme, Erlang Prolog, PARLOG Perl,Ruby,PHP, Python,JavaScript
静的型付け C,ALGOL, Pascal Haskell,F#, Ocaml, Scala   COBOL,FORTRAN, Java,C#,D

調査不足で空欄がたくさんありますが、許してください。

2. Haskell の特徴

さて、 Haskell の特徴について考えていきましょう。 良く言われることとして以下のことがあります。

  • 静的な型付け
  • 遅延評価
  • 純粋関数型 or 参照透明性 or 副作用がない

以下では、それぞれがどういう意味なのか、具体例をまじえて見ていくことにします。

2.1 静的型付け

型とは、あるデータ(関数, 変数, 定数, etc..)がどのように扱われるべきか、を表すものです。
静的型付けを行うことで、コンパイル時に型が異なるようなエラーについては発見できることになります。

2.2 遅延評価

遅延評価 とは、式の評価をできる限り必要になるまで放置する、というものです。

これは非常に強力です。特に、無限リストを簡単に書くことができます。

例1. 無限リスト1 : 1,1,...

ones = 1:ones

例2. 無限リスト2 : 2,4,6,8,10,...

[2,4..]

例3. 2つの無限リストの各要素の和 :

zipWith (+) [2,4..] [1,3..]

これは、2つのリストの要素を、それぞれ足して無限リストを返しています。

例4. 無限リストの評価

これらのリストを実際に使う場合にはどうなるのでしょうか。

take 3 ones -- ones の3番目までの要素を返す

これを実行すると、次のようになる(ハズ。ホントのことはまだわかってない)。

take 3 ones
= take 3 (1:ones)
= take 3 (1:(1:ones))
= take 3 (1:(1:(1:ones))) <- 3番目までの要素がわかった
= 1                       <- 値を返す

普通、プログラミングでは”有限のデータと有限の操作”しか扱えません。 しかし、遅延評価という仕組みによって、擬似的に無限のデータを作っているわけです。

すごい Haskell!!

2.3 純粋関数型

Haskell では、 “関数を引数に適用すること” がプログラミングです。
ここで言う 関数 とは、
同じ引数を与えれば、必ず同じ値を返すもの

関数を『プログラム中のどの場所、どのタイミングで実行しても同じ結果』が返るということから、 参照透明性 と言われます。

ここで、関数の例を上げてみましょう。

例1. succ : 1を足す

succ 1

1+1 つまり結果は 2

例2. 名前 : 引数を取らない関数

language = "Haskell!!"

例3. 少し複雑な関数 : 100 より大きい数を2倍する

doubleNumber x = if x > 100 then x else 2*x
Haskell では if...then...else... も値を必ず返します。つまりelse は省略できません。

次に、Haskell にとっては関数でないけど、Ruby などでは”関数”と呼ばれているものについて、 特に問題だと思われる例を説明しましょう。

例. Haskell では関数でない例 : 擬似コード

total:=0
not_Function(x)={
   count := 0
   repeat
      count := count + 1
      total := total + count
   until
      count = x

   return total
   }

さて、この not_Function は非常に重大な問題のある”関数”です。 それは not_Function を呼び出すタイミングで結果が変わってしまうことです。 例えば、

not_Function(2) <- 結果は total=3
...             <- なんか複雑な処理
not_Function(2) <- 結果は total=6 : どうして結果が変わるか考えてみてください
これは非常に悩ましいことです。
プログラミングするときには、大規模なプログラムや、複数人でのプログラミングの際に、他の人が関数を使ったかどうかを気にしないといけないのでしょうか?
not_Function は、数学的には引数が1個の関数ではなく、明示的に引数として与えていない変数 total にも依存しています。このことが、同じ引数を与えても異なる結果になる原因です。
もちろん、次のように修正すべきという人もいるでしょう。
not_Function(x)={
   total := 0
   count := 0
   repeat
      count := count + 1
      total := total + count
   until
      count = x

   return total
   }
これを必ず、誰もが、巨大なプログラムの最中でできるならやっても良いと思います。
(それが容易でないから、オブジェクト指向が流行ったんだと理解しています。)
オブジェクト指向にしようが手続き型にしようが、変数という状態を変更してプログラミングをします。

そもそも、 “変数を変更してプログラミングすること自体をやめよう、そうすれば参照透明だよ!” 、というアイデアが関数型であり、Haskell なんだと思います。
(その分色んなトリックが必要になると思います。例えば、データベースに読み書きする、といった操作は順番に依存してしまいそうです。)