今回は、星田さんの閑話休題的なこれに関して。
うおお、これならChromeBook(古いのでLinuxは入れられない)でも学習できるやん!これはもはや言い訳不能だな!
あ、多分それだと上手くイカんのじゃないか、って思う(笑)。
それって多分「俺様Lisp」の一つで、Lisp方言ではあるんだけど、ANSI Common LispとかSchemeとかみたいに曲がりなりにも仕様があるメジャーなLispじゃないと思います。
多分思ったように動かない(笑)。
とにかくLispってたくさんあるのです。
例えば、このブログと紛らわしいGOOなんつー名前のLisp方言もある。
ちっとも新しくないのにnewLispなんつー名前のLisp方言もある。
テキストエディタ、GNU Emacsに積まれてるマクロ言語がEmacs Lisp。
唯一の国際標準仕様だけど殆ど実装がないISLisp。
新手のJVM上で蠢くClojureに関してはもう書いた。
処理系の名前じゃない、Lispである事そのものが違う奴らだ。
だから、Lispの場合は「どのLispなのか」キチンと調べないといけないし、仕様のあるヤツは「じゃあ、仕様はO.K. 次はどの実装?」って話に向き合う事となります。
何でこんなにアッチコッチにLispが生えてるのか、と言う理由はただ一つ。
「もし、なんか一つプログラミング言語を作れ、と言われたらLisp語族を作るのが一番簡単だから。」
と言う言語設計者側の事情がある。
C言語でプログラミング言語を作るとしたら「Pythonを発明したり」「Rubyを開発する」よりLispを作る方が簡単だから、なのです。
しかももっと恐ろしい事にLispでLispを書くのはC言語でLispを書くより簡単です。実際、Lispハッカー、ポール・グレアムが作った新しいLisp、ArcはLispで書かれてる(しかもそのLispはRacketだ!) 。
星田さんもそのうち、気の迷いでHoshidaLispを設計するかもしんない。そういう魅力がLispにはある。
言い換えると、実はLispって「全く実用性の無い言語」の反面、「やたら作りたくなる」言語なのよね・・・・・・。つまり「使う」言語じゃなくって「作る」言語、ってのがLispの大きな特徴なの。
そんな背景があるので、「Lisp」って単語だけで飛びつかない(笑)。まず、最低でもANSI Common Lispの仕様に準じてるのか、あるいはSchemeの仕様に準じてるのか調べましょう・・・。
よく「SICP」ってのが目についてたんですよね。無視するつもりだったんですけど、あんまりにも頻繁に出てくるので調べてみるとMITの教科書なんですって?
SICP(シックピーと読む)、つまりStructure and Implementation of Computer Programs(計算機プログラムの構造と解釈)はコンピュータサイエンス史上最高の初級用教科書、との評を受けている・・・まぁ、受けてはいるんだよな(笑)
ただ、今はMITでは使われてません。今はやっぱ時代の流れもあって、Pythonでの教科書が使われています(もっとも「PythonでSchemeを実装しようぜ!」みたいなネタは相変わらずだけど・笑)。
SICP・・・SICPは難しいんだよな。色んな意味に於いて。
まず、非常に重要なのは。
この本、実はプログラミングの本じゃないです。実は全然違うトピックを扱ってる。
実際はこの本が一番力入れてるのは「コンピュータはどういう仕組みで動いてるか」と言う、実はハードウェアの解説本なの。
そのハードウェアの解説にSchemeを使ってると言う・・・要するにSchemeでレジスタマシン(要するに今のCPUの動作原理)を作って、仕組みを理解しましょう、ってのが最終目標なんだよな(笑)。「自分で実際Schemeで"仮想マシン"を作る」ってのがラスボス。
だから、何かプログラミングに重要なテクニックとか・・・書いてないわけじゃないけど、「How to program」を期待して読むと随分と肩透かし喰らいます(笑)。
で、MITで使われてたから「偉い本」なのか、と言うと実際批判も多い。
これ、かなりの確率で「当たってます」。
特に、訳文が読みづらい、って長い間誰からも言われてて、しかも、それは「翻訳も悪いけど原文の英文自体が読みづらいのでは?」と言われてる。
他にも、14ページに渡ってSICPの教育に於ける功罪を語った論文がある。んで、この論文の著者、4人中3人ってのが実はRacketの開発者達なんだよね。
元々、Racket、旧MzScheme/PLTSchemeってのは教育目的で作ったScheme処理系で、SICPの教育効果、とか良く観察してた、って事なんだよな。
んで、実の事を言うと、定期的に色々内容は交換されてきたんだけど、DrRacketのヘルプを辿るとSchemeの教科書系サイトにアクセス出来るようになってるの。
英語のサイトだったりドイツ語のサイトだったりして、日本のサイトは紹介されてないんだけどね(笑)。
SICPに業を煮やしたRacketの開発メンバーが書いた教科書が、そこで紹介されているHow to Design Programs(HtDP)です。
んで、実は上でSICPを批判してた「きしださん」が紹介してたプログラミングの基礎って本はこのHtDPの、平たく言うと翻案本なのです。言語を動的型付けのSchemeから静的型付け関数型言語のOCamlへと変えてコンパクトにした。
んで、両者とも「デザインレシピでプログラミングを進める」っつってるけど、平たく言うと「ユニットテスト頼りでプログラムを書こうぜ」と言うHow to色が強い。最近はこういう「開発手法」が一般化してきたんかな。
とにかくプログラムを書く前に「これが動いて欲しい」と言うテストを先に書く。テストファースト。そのテストをパスすればその部分は完成したんだ、と。
そしてコードを「綺麗にする」のは後回し。こういうのをリファクタリングって言うんだけど、最初にリファクタリングはしない。最初は汚くてもいいんで、まずはテストを通す事を最優先にしよう、って考え方です。
この辺はPythonでもサポートされてるので、この辺を参考にしてみてください。
んで、元の話に戻ると、残念ながら日本語じゃ読めないんだけど、いずれにせよ、今のRacketのオススメ本はRacketの開発者達が書いたHtDPで、言い換えるとScheme系Racketで、Webでタダで読める最強の本はこの本、って事になる。
んで知ってる限りで言うと、MzScheme/PLT Scheme/Racketって流れの中で、独習Scheme三週間は紹介されてたこたぁあるんだけど、SICPってのは一回も紹介された事はないです。
SICPで見られる目覚ましいプログラミングテクニック、ってのもあるこたぁあるのね。
次の3つは何度読んでも良い。
- データ駆動型プログラミング
- オブジェクト指向プログラミング
- 遅延評価プログラミング
ぶっちゃけ、一番役に立つのは「データ駆動型プログラミング」。
ハッキリ言って、ここさえ押さえればSICPの他の箇所は要らん、って程(笑)。うん、テクニック上はそうなる。
ただな、構成がマズいんだわ。
例えば「データ駆動型プログラミング」はSICP2章で扱われて「凄いだろ?この方法」ってカンジで説かれるわけ。確かに「脳内では」凄いんだよな。
ところが前提としてこんな事が書かれてんの。
この計画を実装するには, 演算対型の表を操作する, 二つの手続きput とgetがあると仮定する.
「仮定する」とか言われてもな(笑)。
んで、
ここではputとgetは言語に組み込まれていると仮定しよう. 3章(3.3.3節)で, 表を操作するこの演算や他の演算の実装を見よう.
おいこら(笑)。
つまりさ、putやgetを使わないまま(何故なら使えないから・笑)2章まるまる一章分付き合わせられるわけよ・・・つまり何も出来ないじゃない(笑)。
そう、SICPってね、こういうカンジなの。もったいぶってて回りくどいんだ。
んで、オブジェクト指向。ここも凄い。ただ、SICPのポジションってオブジェクト指向プログラミングに関しては批判的なのね。
それと非常に重要なのは。SICPでは「オブジェクト指向によるプログラミング方法」は全く説明しないの。そうじゃなくってオブジェクト指向の仕組みそのものをプログラミングしようぜと言うとんでもない方向で話を進める。
分かる?SICPってだから「How to Program」の本じゃなくって、むしろ「How to Program some Programming Languages」の本なんだよ。フツーのプログラミング系の本でオブジェクト指向を解説しててもオブジェクト指向の機構そのものをエミュレートしようぜ、なんつー本はまずない。
そしてここである意味Lisp系言語の真髄に触れるこたぁ触れるかも。それは。
ラムダ式万能論だ。
ラムダですかー?ラムダがあれば何でもできる!
と某プロレスラーみたいな事を示し始めたSICP。
つまり、ラムダ式は関数なんだけどデータでもある。オブジェクト指向の「データ」、つまりオブジェクトをラムダ式で表現が可能だ、と言うとんでもない事実を僕らはここで見る事になる。
そして何故にSchemeがオブジェクト指向言語じゃないのか、理由が分かると思う。結局、Schemeではラムダ式さえあればいくらでもオブジェクト指向的なプログラムが書けるのだ。
でもそれは関数型プログラミングと相反するので、同時にSICPでは批判されている。
そして最後は遅延評価。まだまだ最先端のプログラミング技法をSICP程丁寧に解説してくれてる本は無いかもしれん。
ただし、ここも例によって問題がある。
- 遅延評価での乱数列生成を示すのは良い。ただし、実用上、「乱数を返す」には結果破壊的操作が必要なのではないか?と言った疑問には全く答えていない。
- 遅延評価ならオブジェクト指向みたいに「状態を持ちながら」関数型プログラミングが可能になる、と言う「可能性」の話はするが、具体的な方法論は全く提示しないので「〜だったらいいな」と言う話で終わる。
1番はこのブログでPythonを使ってやってみた事がある。っつーかぶっちゃけ、元ネタはSICPだったわけなんだけれども(笑)。
実際問題、Schemeと遅延リスト(ストリーム)を使えば無限長の乱数列を得る事が出来る。すげぇな。でも問題は「だから何?」なんだよ(笑)。
無限長の乱数列は「ずーっとそこにある」。あるのはいいんだけど、じゃあどうやって取り出す?毎回同じトコ取り出せば同じ数になる。じゃあ、乱数列の「何番目」と言う位置を記憶しておこうか?でもそういう「位置を記憶してどっか保存しておく」事自体が実は関数型プログラミングじゃあないんだ。毎回その値を「破壊的変更」するわけだろ?
そして、無限長乱数列は、当然なんだけど、無限長の彼方に行けば行くほど計算コストが跳ね上がるのね。実は遅延リストは場合によっては(と言うより正確に言うと先頭からの位置関係)結構計算コストがかかる。
結局、「遅延評価と関数型プログラミングの相性はバッチリ!」とか言いながら「実用性がゼロ」の例になってんだわ(笑)。なんだこれは、とか思ったね(笑)。
2番も困ったモンだ。遅延評価と関数型プログラミングを組み合わせれば新しいパラダイムが開けるかも・・・とか思わせておいて、結果「かも」って言ってるだけなんだよ(笑)。具体的に「じゃあどうすんの?」っつーのは全く触れていない(笑)。
実は第4章で「遅延評価ベースのSchemeの亜種」を作るんだけど、それはプログラミング言語設計の話であって、「じゃあ、今プログラミングに於いて、どう遅延評価を駆使して行くのか」の答えにはなってねぇんだよ(笑)。だって、遅延評価と関数型プログラミングを組み合わせて何かプログラムしたい、って時に毎回毎回インタプリタ書け、っつーのはおかしいだろ、って話だ(笑)。そこにも実用性観点がない。
言い換えると、遅延評価やりたいならHaskell使え、って話になるのね。なんつー中途半端な結論なんだ、と。それをプログラミング初学者相手に言ってどーすんだ、と(忘れてるかもしれないけど、実はこの本、プログラミング素人向けに書かれた本です)。
とまぁ、僕的には、SICPは「有用な部分もある」けど、全面的に読んで理解したからどーなる、って本でもねぇのね。要するにビミョーに中途半端な結論を持ってきてる、てのがなんつーんだろ・・・色々と歯がゆい本なんですよ。
ただ、前は売ってた本だったんだけど、出版社(桐原書店)がピアソンエデュケーションの代理店やめちゃって、結局この本は日本では廃刊になったんだけど、そのお陰でWebではタダで読めるようになったのね。訳者が無償公開してくれるようになって。
だから「読む分」には「読んでみればイイんじゃない?」とは言えます。あんまマジにならん程度には、って事なんですけど。
それに、Schemeの本ってあんま無いんですよ・・・マトモな本、もね。
だから確かにSICPにある程度頼らざるを得ない、って現実があるのです。
Commonの方は機能たっぷりで「やりたいことは制限なくやらせてくれるような母性たっぷり巨乳タイプ」なのだとか・・苫米地英人さんもCommon Lispってお話だし、Schemeは最初とか二番目に学ぶのは良くないって話もあった・・。けど、このSICPはSchemeを使って説明されてるものらしいんですよねぇ・・どうしたものか・・一つ言えることは僕って「美脚スレンダータイプ」が好きじゃないですかぁ・・Schemeが美脚スレンダータイプであった場合はこっちを選んじゃうかなぁ、と。
うん、Schemeは確かに美脚スレンダータイプだ(笑)。無駄なお肉がない(笑)。
とまぁその話は置いておいて(笑)。
結果的に多分、「両方共そこそこ知る」って事にはなるたぁ思うんだけど。
「学習」って事で考える。つまり、例えば「本を買って勉強する」って前提で考えてみるとするでしょ?
そうすると、圧倒的にANSI Common Lispになっちゃう。理由はSchemeより「マトモな本の」出版数が多く、またそのせいで良書がある、と言う事。
まずね、SICPは取り敢えず置いておいて。
背景をまずは説明すっと、ANSI Common Lispってとにかく仕様がデカいのよ。昔の電話帳くらいデカい。
だから90年代くらいのパソコンでCommon Lispを動かすのは至難の業だったの。当時で言うとメインフレームとかワークステーションがないと動かせなかったのね、デカすぎて。
んで、そんな中でパソコンで使える仕様が小さなLispってんでSchemeが持て囃されたわけ。日本の豊橋技術科学大学でTUT Schemeなんつー処理系が作られたのもこの頃。
それで、ANSI Common Lispの代替としてSchemeが使われた、って背景のせいで、この頃出版された本ってのは関数型プログラミングじゃないんだ。
もっと背景を説明する。
昨今はSICPやら川合史朗さんのお陰で、Schemeと言えば関数型プログラミングをやるための言語、って言うある程度の共通認識を持てるようになった。
しかし、もっと古いLispをルーツとするANSI Common Lispってユーザーはそんなに関数型プログラミングにこだわらないのね。っつーかScheme観点で見ると汚ぇコードを書く人が多いんだ。いや、苫米地さんがどういう風なコードを書くかは知らんのだけれど、多分「関数型・・・」とか言うんだったらどっちかっつーと根はSchemerに近いかもしんない。
んでさ。
ANSI Common Lispの奴らって「Lispは関数型言語じゃない」って言いたがるんだけど、歴史的に言うと明らかに「関数型言語」を意図して生まれてるのね。っつーか当時はそれ以前に「関数型言語」って概念が無かったわけだが。ライバルはFortranしかなかったし。
ここで面白い話があって。Lispが生まれた時、Fortranでやるのを「プログラミング」って言ってたわけ。じゃあLispがやってたのは何か、っつーと「Functioning」つまり、「関数化」だったのね。Lispは明らかに「プログラミングとは違うものをやってた」つもりだったんだ(笑)。
いずれにせよ、Lispは「プログラムを書く」んじゃなくって、数学的な「関数を書くもの」としてはじまってる。
ところが、その「関数を書く」ってのが当時のFortranプログラマは苦手にしてたんだよ。
ANSI Common Lispでprognって機能がある。
例えばこういう風に使うわけ。
CL-USER> (progn
(print 'hello)
42)
HELLO
42
CL-USER
なーんて事のねぇ逐次処理の為のコマンドだよな。Schemeではbeginって用語が使われてるわけですが、要するにprogn内に置かれたLispの式は順番に実行されて、最後の式が実行されたらそれを全体の返り値とする、と。
何てことないでしょ?
んで僕は最初、「式を順番で実行させるんだからprogressの略称なんだろう」とか思ってたんだけど、さにあらず。実はこれ、Programの略称なんです。n番目(つまり最後の)式を実行させてそれを返り値とする為、Program returning the nth expressionの意(多分)でprogn、と。
それで、「え?単に逐次処理する為だけなのにプログラムなの?」とか思うんだけど、言い換えると最初期に登場したLispには逐次処理が無かったって事を示唆してるわけだ。
かなり初期のLisp1.5のマニュアルには次のような事が書いてある。
Lisp1.5では実行されるべきLisp式を含んだ、Algol(※1)のようなプログラムをユーザーが書ける機能があります。
つまり、この前にはAlgol(※2)のようには書けなかった、ってワケだ(笑)。
実際、話によると、逐次処理機能が無かったLispを使う時、Fortranのプログラマは混乱してた模様です(笑)。分かる(笑)?今の「フツーのプログラマ」がLispで混乱するのと同じだ。だって最初期の関数型言語だったんだもん。
言い換えるとだ。「逐次処理が無い言語なんかでプログラムを書けるか!」とFortranとかCOBOLのプログラマが喚いたらしい。だからLispにprogと言う機能が入る事となった。FortranとかCOBOLのプログラマの為に、だな。
でも僕らは「逐次処理が無い言語なんかでプログラムを書けるか!」とか言われても「書ける」って言えちゃうでしょ?Schemeをやってるから。
つまり、逐次処理なんかなくても引数と返り値の連鎖で、
((((...))))
みたいに書けば良い、って事を既に知ってる。でも当時の言い方だと「プログラム」を書く人たちは今も昔も
()
()
()
()
と書かないとダメだ、って言ってるわけ。
さて、Schemeは((((...))))って書く文化、つまり登場当初のオリジナルのLispまで遡る関数型言語文化下なんだけど、反面、総体的に言うとANSI Common Lispは
()
()
()
()
文化圏のプログラミング言語なのね。Emacs Lispなんかもそう。Schemeに慣れると((((...))))じゃないコードを見ると結構クラクラする(笑)。
そんな中でもANSI Common Lispを使った教科書ではSchemeで得られない「関数型言語の良書」があるんだ。
と言うわけで。
ANSI Common Lispなら「これさえ買って読んでおけば良い」って本がまず一冊だけあります。
それがこれ。
マジでこれ一冊さえ買っておけば何の後悔もせん、って本。
本当は原題は「人工知能プログラミングのパラダイム」って言って、今のAIとは違う古典的AIをトピックにしてるトコも多いんだけど、それでも「抽象的な」Scheme本に比べると圧倒的に「実用主義」なんですよ。とにかくマトモにプログラムを書こう、と。それもかなり多くの「プログラムを書く為の」本になってる。SICPとは大違いだし、こんな本はPythonにも存在しないでしょう。
例えば対話プログラムはどう書くのか、とかオセロゲームはどう書くのか、とか。
そしてLisp本らしく、Common LispでSchemeを書いたりPrologを書いたり、しかもコンパイラを書いたりもする。
AIってだけじゃなくって広範囲で「プログラミングのHow to」を詰め込んでいます。
また、作者のピーター・ノーヴィグって人は今はGoogleの人工知能部門にいたと思うんだけど、とにかくプログラムを「ウソッ!」って言う程シンプルに書く人で、読んでてもあまり悩むトコがない。綺麗。そしてこの人はCommon Lisp使ってても「関数型言語」として書いてるんで読みやすい。
マジでこの本1冊あればCommon Lispは事足ります。
ただし、値段が高いんだよなぁ(苦笑)。しかも税率が10%になったんで、1万円超えだ。困ったモンだ。
でもそこまで払えない、って人のためにはLand of Lispがある。これも滅茶苦茶良い本。
実は大して期待せんで買ったんだけどビックリした。そしてこの作者も関数型言語の人だ。ひっじょーに読みやすいコードを書いてるし、なおかつ、「いつ関数型でいつそうじゃないべきか」と言う指南もしている。素晴らしい。
一番良いのは、この本は「ゲームを書こうぜ」と言う本な辺り。いくつかゲームを書いてみて、色んなANSI Common Lispなりのプログラミングテクニックを学ぼう、って本。
しかもSchemeでお馴染みの遅延評価をANSI Common Lispで実装して使ってみよう、って事もやってる。
この本の唯一の難点は、原作者が本のアチコチで描いてるマンガがヘタクソな辺りだ(笑)。
日本人だったら萌え絵描けるヤツに依頼するよな〜、とか思った(笑)。畜生、だからアメ公は(ry
ちなみに、翻訳者はなんでも再帰の川合史朗さん。
ANSI Common Lispだとこの2冊がダントツで良いです。
っつーか、プログラミング言語を見渡すと、やっぱ圧倒的にマイナー言語なのに、2冊も良書がある辺りが凄い、ってのがANSI Common Lispの良いトコなのです。
しかも両書共「一冊あれば他は要らん」的なクオリティです。あと、勉強するとしたら仕様書読んだ方がマシ、って事になる。
なんかPythonのプログラミングどこいった?って感じなんですけど
いや、全然心配せんでエエです。
上のピーター・ノーヴィグ博士も今やPythonistaですからね。
LispやってからPython、ってのは全然不思議でも何でもない。
っつーか、「C言語からPython」の人は、いっつも言うけど、かなり大掛かりな大間違いばっか言ってるけど、Lisp経験者はそれほどでもないでしょ。
CよりもLispやった人の方が圧倒的にPythonと親和性が高い。
だからその辺は気にせんで良いです。
Lisp触る度にPythonの切れ味はむしろ上がるんじゃないですかね?
と、今回はこの辺で。
※1: 今の「手続き型言語」のルーツと言って良いが、あまりにも仕様が巨大だった為、実装が殆どなかった。
プログラミング言語Pascalは大雑把過ぎる言い方をするとこのAlgolのサブセットである。
また、見た目は全然違うが、C言語もAlgolの影響を受けてる。
そしてSchemeが採用した「レキシカルスコープ」と言うのもこのAlgolから持ってきた考え方である。
※2: Algolはヨーロッパ主導で定義されたが、ここにアメリカから参加した人の一人に、Lispの発明者、マッカーシー博士もいた。そしてAlgolでも「関数」を取り入れたわけだが、1970年のPascalの登場まで、手続き型言語ではそこまでポピュラーな「手法」には成り得てなかった。
そしてPascalの登場以降でさえ、やはり「関数作成」は、今じゃ信じられないが、プログラマ達に嫌われてて、Lisp1.5が導入したような「逐次処理型」、言い換えるとBASIC等の「バッチ処理」プログラムが主流だったのである。
C言語登場で一変したのか、と言われるとそうでもなく、C言語開発者達の述懐によると、C言語を開発したAT&Tやベル研の職員達も「何か遅くなりそう」と言ってC言語の「関数」をずーっと敬遠してたそうだ。
そこでC言語開発者達は「遅くならないから!」と職員たちを騙し(実際は若干遅くなる・笑)「関数を使うプログラミング法」を拡散するのに相当苦労した、と言うようなエピソードがある。
結局、今も昔も「新開発のプログラミング手法」と言うのを人々が受け入れるのは相当抵抗がある、と言う話である。