Login
Immutable PageDiscussionInfoAttachments
alstamber/HaskellSeminar1

MMA

Haskellを学ぶにあたって

Haskellという不思議な言語を学ぼうと思った皆様にいくらか最初にお知らせがあります。

まずHaskellは非常に奇妙な言語です。きっと最初は一筋縄では落ちてくれない、難攻不落の高嶺の花のように思えることでしょう。
しかしその壁を超えることが出来れば、非常に興味深いプログラミングに対する考え方を身につけることができます。
その知識はきっとあなたにとって役立つものになることでしょう。

Haskellを学ぶにあたって気をつけてほしいことは、(もしあなたが今までに何か別の言語を学んだことがあるのなら)その知識はおそらくまるで役に立たないであろうということです。
HaskellはC言語のように命令の並びを書き並べる言語ではありません。オブジェクト指向言語のようにオブジェクトをプログラムの基本概念として据えているわけでもありません。
いっその事新しくプログラミングを一から覚え直す、という気持ちで取り組むほうが飲み込みが早いかもしれません。

Haskellの概要

Haskellは純粋関数型プログラミング言語と呼ばれるプログラミング言語の一種です。

関数型言語とは

関数型言語というのは一体何なのでしょうか。
関数型言語とよく対比される存在として手続き型言語という物が存在します。C言語やJavaなどよく知られている言語の大半は手続き型の言語です。
手続き型言語では命令の並びを書き並べそれをコンピュータに与え、そのまま実行してもらうというスタンスを取ります。
コンピュータは命令を実行することで自分自身の状態を変更します。コンピュータの状態としては、たとえばメモリに格納されているデータが挙げられます。
命令の並びがコンピュータの状態を変更していき、最終的に得られたコンピュータの状態がプログラムを実行した結果である、というわけです。

関数型言語ではコンピュータに命令、すなわち何をするかを教えるのではなく、答えがどういうものであるかという事を教えることでプログラミングをします。
たとえば階乗を求めたいときは

階乗というのは1からnまでの積である

という定義そのものをプログラムとして与えます。このプログラムに書かれた答えの定義を元に、コンピュータはよしなに答えを求めてくれる、というしくみになっているわけです。

こういった答えの定義を関数型言語では「関数」という形で与えることになっています。
ここでいう関数というのはCやC++に出てくる関数とは違うものです。どちらかと言えば数学で言うところの関数に近い存在です。 (数学の世界で汎関数や作用素と呼ばれるものにあたります)

数学の世界の関数の例を一つ挙げてみましょう。

f(x) = x + 1

この関数はある捉え方をすれば、xというものを入力としてもらったとしてその時の答えはx+1ですよ、という定義をしているようにも見えます。
この考え方をもっと色々なことができるように拡張したのがHaskellで言うところの関数です。

Haskellの変数

Haskellにも他のプログラミング言語と同じように変数という概念があります。
ただしHaskellの変数はちょっと性質が違います。Haskellの変数には一度代入すると別の値をもう一度代入できないという性質があるのです!
このことはHaskellの関数が数学の関数に近いという話を思い出せば、割と納得しやすいです。
数学の世界では一旦xという文字を導入したら、それが指すものが別のものに変わることはありませんね。それと同じです。

参照透過性

Haskellはただの関数型言語ではなく、純粋関数型言語と呼ばれる言語です。純粋というのはどういう意味なのでしょうか。
純粋というのは副作用を持たないという意味です。副作用というのはある手続きがその手続きの外側に影響をおよぼすような事を言います。
たとえば

   1 a = 1
   2 
   3 def f()
   4     a = 2
   5 end

上のプログラムではfというメソッドがメソッドの外側にあるaという変数を書き換えているので、副作用があります。

副作用の有名な例として入出力があります。画面に何かを出力するという処理はプログラムの外側のディスプレイに影響を及ぼしているので副作用です。

副作用を排除すると何が良いのでしょうか。
副作用を排除することで関数は参照透過性という性質を持つことができます。参照透過性というのは、関数を計算した結果が与えられた値からのみ決定されるということです。
副作用があると、関数の外側の影響を受ける可能性があるので、この性質が成り立たない可能性があります。
この性質が成り立つことで、プログラムを書く際に関数とそれに与えられる値のことだけを考えれば良くなるので、考えなければいけない範囲を狭めることができるという利点があります。
一方で入出力など頻繁に使う処理が副作用を伴っているのも事実で、このようなものをHaskellで扱うためには少し特殊な方法を使う必要があるという面倒な点もあります。

遅延評価

副作用を排除したことでもう一つHaskellは他の言語とは違う特徴を持つようになっています。それは遅延評価という性質です。
遅延評価というのは、必要になるまでプログラムの処理を行わないという性質です。

Rubyなどの言語は与えられたプログラムの文を与えられるがままに実行します。それに対してHaskellは必要になるまでプログラムに記述された処理を行わず、かつ必要最小限のことしか処理しないという性質を持ちます。
まるで可能な限りレポートを後回しにしようとする一部の一年生諸君にそっくりですね(一部であることを願っております)。それ故、遅延評価というのは英語ではlazy evaluation, すなわち「怠惰な評価」と呼ばれます。

遅延評価を採用できるのは、Haskellが副作用を持たないように作られているからです。結果が与えられた値だけで決まることが保証されているので、いつその計算をするかを気にしなくてよいということです。

たとえば数の配列

   1 xs = [1, 2, 3, 4]

というものを考えます。これに対して、すべての要素を2倍にした新しい配列を返してくれる関数doubleMeがあるとします。要素をすべて8倍にしたいなら次のようにすれば良いですね。

   1 doubleMe(doubleMe(doubleMe(xs)))

Rubyで上のようなプログラムを実行すれば、まず配列を一番内側のdoubleMeに渡し、それを2倍にしたものを作って返します。その結果が真ん中のdoubleMeに渡されて……ということが繰り返されます。

Haskellでは上のプログラムが与えられても、まだ結果を表示する必要がないので、プログラムが実際に実行されることはありません。
実際に結果を表示する必要が出た時に、Haskellは初めてしぶしぶ一番外側のdoubleMeを呼び出します。一番外側のdoubleMeは真ん中のdoubleMeを呼び出して、結果を教えろと言います。真ん中のdoubleMeは同じようなことを一番内側のdoubleMeに対して行います。一番内側のdoubleMeはしぶしぶ最初の要素を2倍したものを返します。真ん中のdoubleMeはそれを受け取って、一番外側のdoubleMeに更に2倍にしたものを返します。一番外側のdoubleMeはそれを受け取って最初の要素が8であるという結果を返します。

Haskellが持つ遅延性のお陰で、このプログラムにおいても配列を3回スキャンする必要はなく、1度スキャンするだけでプログラムの処理が終わるわけです。

Haskellと型

HaskellはRubyとは違い、静的型付け言語と呼ばれるものに属します。
静的型付け言語というのはプログラムの中にある変数や値のどれが数でどれが文字列であるかということが、プログラムを実行する前にわかるようになっている言語のことです。
Rubyはプログラムを実行してみて初めて変数や値の型がわかる言語なので、動的型付け言語と言います。

静的型付け言語というのは、型に縛られている感じがして、最初は気持ち悪いと思うかもしれません。
しかし型というものが予めわかっていると、データの種類が一致していないことに起因するバグを予めエラーとして見つけておくことができます。
その結果バグ取りがしやすいという利点があります。

型推論

プログラムを実行する前に型がわかっているためには、普通、プログラム中に出てくる値や変数に明示的に「これは○○という型ですよ〜」と書いてあげる必要がります。
C言語はそのいい例です。しかし型をきちんと記述するというのは非常に面倒くさいものです。
そこでHaskellには型推論というシステムがあります。型推論というのは型をちゃんと書いておかなくても、Haskellが勝手に「この変数は○○型だろう」と推測してくれる機能です。
Haskellの型推論は非常に強力なので、大抵の場合型を明示的に書かなくても良いようになっています。

たとえば

a = 5 + 2

というコードがあったとします。このコードをみればaが数の型になるだろうということがわかりますね。Haskellはこのような推測を勝手にやってくれます。

alstamber/HaskellSeminar1 (last edited 2013-06-24 18:31:53 by alstamber)