bitterharvest’s diary

A Bitter Harvestは小説の題名。作者は豪州のPeter Yeldham。苦闘の末に勝ちえた偏見からの解放は命との引換になったという悲しい物語

なぜHaskell、なぜ圏論なのか

1.初めに

これまで、さまざまな言語でプログラミングしてきたが、一番満足しているのはHaskellである。なぜという問いに一言で答えるならば、バグが入りにくい、あるいは、プログラムが信用できるということだろう。
f:id:bitterharvest:20161002140825p:plain
この記事の前に、量子力学の世界をHaskellで構築することを試みた。連続系についてはまだ説明の途中であるが、その基本となる離散系については完成している。量子力学は物理の世界でも難しい分野の一つだ。概念的に複雑な世界を記述しようとすると、とても、抽象度の高いプログラミング言語を必要とする。

現在のHaskellは、圏論(category theory)という数学を応用したプログラミング言語である。他の学問と比較すると、数学は抽象度が高い。圏論は、その中で、最も抽象度が高い分野の一つである(圏論よりも抽象度が高いのは最近話題になることが多いホモトピー型理論だ。この理論がプログラミングの世界で使われるようになると、プログラムの証明はもっと簡単になると予想している)。

Haskellは抽象度がとても高いので、応用分野に適した枠組みを提供するのにとても適している。量子力学のような高度な物理概念も、Haskellを用いることで、量子力学用のフレームワークを割合と簡単に提供することができる。とはいっても、量子力学圏論Haskellの3つを理解していなければならないのでハードルは高い。それでも、それぞれに精通していれば合理的な時間の中で完成させることができる。しかし、他の言語、C++Javaでと言われると二の足を踏む。取り掛かる気にもならない。本格的な作業を始める前に準備するものが多すぎて、時間もかかりそうだし、良いものができる保証もない。

ブログを見ていると、Haskellに挫折した人が多い。恐らくは、その基本となっている概念、即ち、圏論を理解していないためだと思われるので、圏論について、もう一度説明してみたいと思う。

Bartosz Milewskiという方を知っているだろうか。 Haskell圏論に関連するブログを検索すると時々見かける名前だ。

ブログのプロファイルによれば、ポーランドで教育を受け、理論物理で博士の学位を授与されている。その後、欧米でポスドクを経験し、マイクロソフト検索エンジンの開発を行ったそうだ。会社の方がインターネットビジネスに消極的であったこともあり、うまくいかなかったそうである。その後、Reliable Softwareという会社を立ち上げ、現在は、ワシントン大学情報科学の大学院に在籍しているとのことである。写真を見る限りでは、ある程度、歳が行っているように見えるが

彼が、シアトルで、プログラマーのために、10週間のコースを開催中である。講義名は、Category Theory for Programmersである。講義の内容はYouTubeで紹介されている。また、書籍での出版も望んでいるようで、途中までの原稿もブログに掲載されている。

さて、Haskellを皆さんに勧める理由をもう少し詳しく説明しよう。

プログラミングを学んで最初に戸惑う記述は
\begin{eqnarray}
a=a+1
\end{eqnarray}
だろうと思う。方程式の記述に慣れてきた身には
\begin{eqnarray}
3x=2x+1
\end{eqnarray}
という記述は抵抗なく受け入れることができる。左辺の\(3x\)と右辺の\(2x+1\)は等しいと訴えている。この時、\(x=1\)だと理解できる。しかし、同じように考えたとき、\(a=a+1\)を満たす\(a\)はないよということになる。

プログラミングしていて出会うこの悩ましい記述を理解するためには、コンピュータのことをもう少し詳しく知っている必要がある。コンピュータにはプロセッサとメモリがあって、プロセッサは計算し、メモリはデータを記憶する(正確に言うと、プログラムもここに記憶される)。メモリには、番地がついていて、データを取り出す時も、データを格納するときも、この番地を用いる。プロセッサはコンビニエンスストア―のキャッシュレジスターと同じ役割をする。プロセッサには、レジスターがあり、データを一時的に記憶することができる。また、レジスターのデータに対して四則演算を行うことができる。その時、足す数、引く数、掛ける数あるいは割る数などは、メモリーより読みだす。メモリから読みだされたデータを使って、レジスタにあるデータに対して指定された演算を行う。その結果は番地を指定してメモリに記憶することができる。

\(a=a+1\)は、\(a\)という名前で呼ばれている番地から、そこに格納されているデータを取り出し、それをプロセッサのレジスタに移動する。次にレジスタの内容に\(1\)を加える(\(1\)という値はメモリのどこかにしまわれていて、ここから取り出して用いる)。ここまでの手続きが、右辺で示されている。右辺の処理が終了すると、左辺の処理に入る。レジスタの内容を\(a\)という名前で呼べれている番地に記憶する。

プログラミングで用いる記述は、通常は、数学で用いるものと異なっていることが多い。このため、コンピュータの構造やコンピュータの内部での処理を素直に記述するアセンブリ言語を学ばないと、初学者は独特の記述をなかなか理解することができない。

\(a\)は変数と呼ばれるが、変数の値が変わることが許されているものをミュータブル(mutable)という(そうでないものをイミュータブル(immutable)という)。多くのプログラミング言語は、変数がメモリの格納場所を表しているために、ミュータブルである。

しかし、コンピュータが広く使われるようになるにしたがって、アセンブリ言語ではプログラミングしにくいので、いわゆる高級言語と呼ばれるものが開発された。プログラミングの世界でも抽象化が行われるようになる。抽象化とは、異なっていると思われる複数の世界の中から、共通する性質を見つけ出す作業である。抽象化することによって、それまで別々のプログラムとして実装されていたものが、共通する部分は同じプログラムで実現できるようになり、開発費や信頼性の向上につながる。

各種の高級言語が出る中で、オブジェクト指向の概念は大きな画期であった。そこでは、プログラムはオブジェクトの集まりで、オブジェクト間でメッセージを受け渡すことで処理が進行する。オブジェクトはメッセージを受け取ると、その処理を行う。その処理は外部に対しては隠蔽されている。このため、もっと良い方法が見つかれば、処理の中身を自由に変更できるという素晴らしい利点を有している。オブジェクト指向の概念は、技巧的なプログラミングの世界を科学的・工学的にするという一翼を担った。僕自身もとても良いプログラミングスタイルだなと考え、情報科学部を設立したときに、1年生からJavaを学べるようにした。

しかし、変数はミュータブルであったために、並行処理が盛んになるに従ってその問題点が浮き彫りになってきた。オブジェクト指向では、オブジェクトは状態を有する。そして、状態はミュータブルである。状態の変更はオブジェクト内部の処理であるため、隠蔽され、外部からは見ることができない。並行処理では、オブジェクトが複数から同時にアクセスされることを許す。即ち、複数のものが状態を共有する。その結果、同時に状態の変更を要求されることがある。これは、競合と呼ばれる問題で、適切に解決しようとすると大変面倒なことになる。解決を提案した論文がたくさん出版されていることからも難しさが分かる。

実際、辛い経験もした。Webを用いて、経理にかかわる社員のだれもがアクセスできる会計システムを2000年の初めごろに開発した。それは、時間遅れのない会計情報が得られるということを売りにしたシステムであった。このため、トランザクションが生じたとき、会計データが瞬時にそれを反映することが要求された。その結果、データ更新の競合を避けるという面倒くさい作業が付与された。データの書き換えは一瞬であるのにもかかわらず、書き換えるための安全性を保障するために、余分な沢山のプログラムを必要とした。無事に完成はしたが、もう二度と関わりたくはないとも思った。

Haskellはイミュータブルである。従って、オブジェクト指向言語が持っていた並行処理を行う上での問題点は回避される。ただ、変数の値を変えることに馴染んでいるプログラマーは最初は戸惑うことと思う。変数を便利に多用していたことと思われるので、手足をもがれたように最初は身動きできないかもしれない。プログラムを再帰的に記述するというのが、イミュータブルな言語での解決策である。しかし、これに慣れるには時間のかかる人が多いであろう。

しかし、抽象化はとても大切な概念である。抽象化することによって、必要ではなさそうな細かすぎる問題から回避され、その本質だけに集中できるようになる。これにより、開発の時間は短縮され、信頼性は一段と高まる。デバッグのいらないプログラム開発を目指すこともできるようになる。

それでは、次回から圏論の話から始めよう。