2008年9月22日月曜日

学校でしか教えてくれないプログラミング言語のこと

Scheme(スキーム)というプログラミング言語について初めて知ったのは、大学2年生のときです。理学部情報科学科に進学し(東大には進学振り分け(通称:進振り)という制度があって、大学2年生の前半までは一般教養を学び、それ以降から専門課程に進みます)萩谷先生担当のプログラミング演習がSchemeとの出会いでした。(当時のものとは大分違いますが、参考までにSchemeの講義資料へのリンクです。http://www-ui.is.s.u-tokyo.ac.jp/~hara2001/scheme/)

おそらく、ここで学ばなかったらSchemeのように一般に知られていない言語には触れることもなかっただろうし、Emacs Lispのプログラム(Schemeと同様の構文です)を自分で書くこともなかったように思います。Emacsというテキストエディタは、自分好みの機能をLispを用いて実装することが醍醐味です。今でも、簡単な計算はLispで書いて、Emacsを電卓代わりにしています。(以下は、階乗の計算をちょこっとLispで書いてみた例。xのn乗を再帰で計算する関数pow(x, n)):
括弧が多くて「何だこれは?」と思われそうですが、括弧の対応付けはエディタが視覚的にサポートしてくれるので、式そのものの見た目ほど入力は大変ではありません。

実用性という意味では、Schemeは非常にマイナーな言語だと思います。けれど、再帰であったり、リスト構造であったり、環境(変数などの状態を運ぶもの。継続とかもそう)であったり、λ関数(クロージャと言ったほうが良いかな?)など、プログラミングで重要な概念を学ぶのには良い言語だと思います。型を学ぶにはOCamlなどML系の言語の方が適しているので、そちらを授業に使う大学も多いようです。


Schemeに関しては思い入れが深く、僕にとってはEmacs(Windows版はMeadow)を使い始めたきっかけでもあるし、情報科学科のCPU実験で、Scheme言語を使って、Scheme言語のコンパイラ(自分達で作るCPU用のアセンブリを生成します)を作ってしまうくらい深く関わった言語でもあります。言語の仕様書(当時はR5RS)もそれなりに読みました。

それを最後に、久しくSchemeから離れていたのですが、思い出させてくれたのが以下の公開質問
「プログラミングに詳しい人に質問です。大学でプログラミング経験の学部一年生向けにプログラミングを教えることを想定しています。週1コマ×半年程度の限られた時間で、プログラミングとはどういうものかという本質を教えたいのですが、どの言語を使うのが適切でしょうか。」
ここでは、プログラミング言語の種類をあまり知らない人を解答対象からはじくために、以下のような質問肢が用意されています。一般の人よりは僕はSchemeを知っていると踏んで、これを読んだときの僕の思考過程(赤文字)も併記しました。

* Schemeは1.5からオートボクシングの機能をサポートした
auto-boxingってJava…
* Schemeはインデントによってブロックを表現する
そんなわけない。括弧だよ
* Schemeは多くのレンタルサーバに標準でインストールされている
もしそうだったら凄い!
* Schemeでは関数がファーストクラスのオブジェクトである
Schemeで言うオブジェクトって何?単にデータという意味だとすると'A (文字を表すsymbol)とかあるし…。(cons 1 2)だと、関数が1と2のペア表すデータ型(オブジェクト)と捕らえることができるけど。。。
* Schemeの文の終わりはセミコロンである
違う違う。
* Schemeは純粋関数型言語であり、副作用はモナドでくるむ必要がある
副作用がない関数型言語って、関数の外側の変数を変更できないってことだよね。そんなことはなかったような、、そもそも、モナドって何?と焦りグーグル検索。。これHaskellに出てくるのか。。
* Schemeは型に厳格なため整数の加算と浮動小数点数の加算の演算子が異なる
そういえばSchemeで型を意識したことがない。。。
* Schemeは関数の呼び出し時に括弧を省略することが出来る
そんなsyntaxあったかなぁ。。。たぶんない。
* Schemeのマクロ定義には#defineを使う
C/C++だよ、それ。(define f (x) ...)はあるけど。
* Schemeの言語仕様はキューマシンとしての実装に適しているため並列化が容易である
キューマシン -> 並列化が容易というロジックにとっても飛躍を感じるなぁ。副作用無しの純粋関数型の範囲なら、Googleのmap-reduceの概念のベースになっているように簡単に分散できるけど。。。
* Schemeのブロックはbeginで始まりend.で終わる
これは違う。
* SchemeのコンパイラとしてはGHCが有名である
GHCって何?聞いたことない。Googleで検索…またHaskellですか。。。知らないよ(涙)
と、まぁ、大変でした。なぜって外部情報で補完しないと正解が出せない質問がたくさんあるから!「多くのレンタルサーバーに」って。。。「多くのレンタルサーバー」の現状を知らないとわからないし。本格的なSchemeの使用例が見当たらないって理由で推測はできるけれど。他にもSchemeにはまだ僕の知らない構文があるのかも…とか不安に思ったり。それでも、Googleで検索すればそれぞれ1分と経たずにわかる程度のことだと思います。とても懐かしく、かつ、知らないことも多くあったので知的な刺激になりました。

プログラミング教育用としての関数型言語

こと、教育のためのプログラミング言語の選択に関しては、ネットワークに接続したり、データベースを使ったプログラミングが必要なら、現時点では迷いなくJavaを選択します。動作させやすいこと、使っている人が多いためEclipseのような使いやすい開発環境が整っていることが選択の理由になります。

Scheme, Lispのような関数型言語の肝は、副作用がないという点だと思っているのですが。たとえば、Googleが超並列計算に使用しているMapReduceのフレームワークでは、

map(入力を2倍にする関数f, {1, 2, 3, 4, 5}) => {f(1), f(2), f(3), f(4), f(5)} = {2, 4, 6, 8, 10}
reduce(入力を足し合わせる関数h, {2, 4, 6, 8, 10}) => 30

というmap, reduceという2つの手続きで計算を行います。map部分をScheme(Lisp)的に書くと、
上のようになります。map関数では、入力リストの先頭(car)から順に関数fを適用していきます。たとえば、以下のような式

の結果は、{2, 4, 6, 8, 10} になります。関数の引数に関数f(lambda文を使ってxを2倍する関数を定義している)を渡して、内部では{(* 1 2), (* 2 2), (* 3 2), ... }を計算しています(+演算は前置記法)。ここで大切なのが、map関数内で、関数fの実行順に制約がないということです。つまり、個々の関数fの実行では副作用がない(!)ので、どの順番で実行してもよく、この部分を言語の実装次第で並列化することができます。

GoogleのMapReduceではこのような副作用がないというプログラムの意味(semantics)を活用して、個々のmap操作を別々のノードに計算させて超並列化を可能にしています。関数型言語を用いるとこの副作用のない部分を明確に記述できますし、驚くほど多くの計算がmap-reduceの組で書けるようです。例えば、forループで先頭から順に辿らなくても良い場合が見つけやすいと思います。Googleは並列化を促進するために、入力listのコピーをいつくかのノードに分散して配置する工夫も行っています。検索エンジン用の索引構築なども、このMapReduceのフレームワークで再実装したとMapReduceの論文には書かれています。

再帰の概念は他言語でもよく見かけますが、関数型言語の特徴である副作用がない(データすら関数で表現されている)コードの書き方というのは、手続き型言語であるJava, C/C++など、実際の開発現場でよく使われるプログラムの書き方から入った人には、とても気付きにくいものだと思います。関数に関数を渡すという概念も、何十年も前の関数型言語の研究から、他に移殖されたものです。

これらの通常では気付きにくい概念や、既に一般に当たり前のように使われていることの裏にある理論を教えるのが大学のあるべき姿であると考えます。特に、簡潔な記述で速く動くことに主眼に置くcomputer scienceの分野では、プログラミングの授業も、言語を問わず、一般の技術者向けの勉強本では深く触れられていないような、本質の部分をカバーすべきでしょう。そうすることで、近い将来、新しいプログラミング言語が出てきても、プログラミングの歴史と比較して、新しい部分、重要な技術などをはっきりと認識できるようになり、言語を使いこなす能力をもったプログラマを育てることにつながるのではないでしょうか。

(追記)

そもそもプログラミングの本質って?

プログラミングの本質をどこに据えるかで言語の選択は変わりますね。省メモリ・並列化が主眼なら副作用、同期などが重要なのでアセンブリ、C/C++などlow levelに近いもの。オブジェクト指向ならJavaなどデザインパターンの威力が見せられる言語が良いと思う。簡潔さ、手軽さが大切ならPerl, Rubyとか。関数型という意味では、Scheme, Lispも面白いけれど、Ocamlも良さそう。Python, Haskellとかは詳しくないので…。

教育で大切なこと

実際、講義の中や在学中に本質を理解しきる必要はないと思うのです。僕自身、関数型言語とMapReduceの関係なんて、卒業した後に初めて理解したものです。気付く人もいれば、数学と同じでまったく使わない人もいる。ただ後になってもう一度学習しようと思ったときに、入り口の部分を見せてもらっているのとそうでないのとでは、学習効率(学ぶことへの抵抗感)が相当違います。だから、1から10までを教えるのではなくて、1を見せることが「教育」で本当に大切な事だと感じています。

0 件のコメント:

License

Creative Commons LicenseLeo's Chronicle by Taro L. Saito is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.1 Japan License.