2014年11月3日月曜日

『プログラミングGauche』を読んだ

フムフムヌクヌクアプアア本としても知られる『プログラミングGauche』を最近読みました(今3周目を読んでます)。

読後の感想ですが、ひとことで言えば『Scheme すごい、Gauche 楽しい』って感じです。
プログラミング自体の初心者には向きませんが、ほかの言語を知っていれば難易度の勾配もゆるやかで、無理なく読み進められる良い本だと思います。

とくに面白かったのは7章(手続き)、18章(マクロ)、 19章(継続)あたりです。
関数プログラミングに触れたことがない人は、7章まで読むとその面白さを体験できると思います。Common Lispを触ったことがある自分でも、Lisp-1での関数プログラミングの気持ちよさは特筆すべきものがありました。あとLispのパワーを顕著に体感できるのが18,19章です。こういうのを知ってしまうと Scheme ならアレができるのに、とか思ってしまいそうです。

以下、Scheme/Gauche/本書に関して面白かったところとかをつらつら書いてみます:

LISP-1気持ちいい & マクロについて

Scheme は Lisp-1 なので、手続きを渡すときに #' が要らないのと、クロージャを作ったあと funcall なしにそのまま呼べるってのが結構気持ちいいです。オペレータ位置に手続きを返す手続きを色々かけるし、わざわざマクロにしなくてもdefineで手続きの別名をつけられたり、非常に楽しく、ストレスなくプログラミングできます。書きたい通りに書ける感覚というか、書いてて俺SUGEE感を味わえるというか。

「list みたいな(本来の手続きlistを隠してしまう)変数名使えないじゃん」ってのもそんなに気になりませんでした。あえて list を scheme の list 手続き以外に束縛してたとしても、レキシカルスコープなので書いている手続きの中でさえ気をつけていればいいことだし。

Lisp-1 で何か困ることあるかなー、と考えてみたんですが、もしかしてマクロ(構文)がひとつのネックになるかもしれません。

define-syntax で define-syntax + syntax-rules で(20150118更新) 定義した健全なマクロは何の問題もありません。しかし define-macro で定義した古典的マクロの場合、マクロを展開した後に現れる手続きがほかのものに束縛されている環境では当然意図した挙動にはなりません。おそらくアナフォリックマクロや、細かいマクロを積み上げてDSLを作るような環境だと妙なバグに悩まされる可能性が高いかも。

Lisp-2 な Common Lispであってもlabelsなどで関数の名前空間にローカルな束縛を作ることは多いと思うのですが、Lisp-1ではそれに加えて変数としての束縛も衝突してしまうわけで、実際の名前の衝突しやすさでいうとやはり Lisp-1 のほうが確実に多そうです。

マクロを追究するなら Common Lisp と言われている(エイリアンじゃないほうのLOLに書いてあった気がする)のはこれが理由なんですかね?

あとは syntax-case とやらで解決できるんでしょうか? syntax-case は本書が書かれたころのGaucheに入ってないこともあって、解説されていません。(2014年11月現在の最新版Gaucheにもまだ入ってなさそうです)

(20150118追記) Gauche には開発版ですが explicit renaming と呼ばれるタイプのマクロシステムが導入されたようです。これで必要に応じて変数キャプチャを防ぎつつ、新しい束縛を作るようなマクロも書けるようになっています。

ついでにマクロについても書いてしまうと、健全なマクロ syntax-rules (20150118更新)はパターンマッチで変形後のコードをディスパッチできて、さらに同じ define-syntax syntax-rules (20150118更新)内で再帰的にマクロを呼び出せるってのが非常に強力だと思いました。マクロの再帰呼出しはCommon Lispでもできますが、パターンマッチとの組み合わせがこんなに強力とは。変数補足を気にする必要がないってのもお気楽にマクロを書くことができて大変良いですね。

継続がすごすぎる

これは衝撃でした。
Common Lisp の Condition を知った時もびっくりしましたが、こちらはそれ以上の驚きでした。「こんなことができるのか・・・」と。

そして素の Scheme にはファーストクラスの継続はあるけど、 Condition はない。なぜ Scheme が「ミニマリストの・・・」と呼ばれているのかわかった気がします。例外処理やCondition は継続というプリミティブな要素とマクロがあれば作れそうですからね。(Gauche(SRFIにも?)にはCommon Lisp風のConditionがすでにありますが)

継続ベースのアプリケーションサーバ Kahua を使ったWebアプリ作成の章もあって、非常に興味深い内容でした。

普通の言語でのWebアプリはリンクのところではリンク先のURL等で実行すべき処理を意識し、渡すデータもURLのパラメータにするかセッションへのデータ格納・取り出しを意識しないといけませんが、 Kahua の場合は生成箇所にリンク先でやってほしい処理を(必要ならlambdaっぽくその場で書き下して)指定できます。渡すデータもその場でその処理へのパラメータや環境としてシームレスに渡すことができ、まるで魔法です。

あえて言えば、その継続の環境フレームを知っている単一のGaucheインスタンスで処理される必要があるため現在流行の(?)分散環境では難しそうだなという点が気になりますが、小規模な(といってもパーシステントなロードバランサで何とかなる程度の)環境なら問題になりませんし、オブジェクトは完全にイミュータブルにするという制約のもとで環境フレームを取り出してシリアライズし、KVストアに突っ込んだりできれば分散環境でも面白いことができるかもしれません。

その他

あと特筆すべき点は Gauche の Unix 環境との親和性の高さでしょうか。正規表現リテラルがあるとか sys-* でほとんどのシステムコールを呼べるとか、UNIXでの普段使いのスクリプトとしてすごくありがたい機能があって嬉しいです。コマンドとしての呼び出しもmain手続きを書くだけだし、イメージ書き出ししなくても起動が速い。他にも色々ツールがそろっていて、ちょい書きのスクリプト用言語としてはGaucheだけでかなりのことができるので大変使いやすいと思いました。

注意点

本書は書籍としての品質が高く、挫折ポイントや読んでいて気になるところは皆無です。後半assqなどが解説なしに出てきますが、名前とあつかうデータ構造から想像はつきますし、問題ありません。

あえて上げるとすれば、dbd-mysql やKahuaといったGauche外のライブラリのインストールなどで、もしかしたらうまく動かない場合があるかも、という点です。一番確実なのは Gauche およびそれらのアプリのバージョンを本書に記載のバージョンに合わせることですが、私の環境では最新リリースの Gauche 0.9.4 でも Gauche-dbd-mysql は Github にあるリポジトリのmaster先端、 Kahua は同じく stable1_0 ブランチを使うと本書の解説通りに動かすことができました。

あと、本書は素の Scheme と Gauche 独自の拡張部分とをハッキリ区別して書かれているわけではありません。Gauche にはデフォルトでロードされている SRFI もあり、 Scheme の仕様そのものや他の処理系について学びたい場合には最適解ではないかもしれません。とはいっても、「R5RSでは」「Gaucheでは」「これは SRFI-xx の機能で」とか要所要所に書いてあるので、注意深く読めば素の Scheme と拡張部分の区別はつくはずです。

まとめ

本書は日本語で読める Scheme 処理系の解説として、かなり楽しめる本です。Lisp を触ったことのない人はもちろん、 Lisper でも Scheme やったことないって人は本書を読んでみると良いと思いました。

0 件のコメント:

コメントを投稿