はじめに
大抵のプログラマはLISPが好きだと思うが、メインで使う人は少ない。その理由として、複合的なデータ(例えば座標とか)の扱いが挙げられるのではないだろうか。
LISPには一般的にクラスみたいな複合データのファーストオブジェクトがないので、配列で賄うこととなる。大抵の場合はdefine-structみたいなマクロが用意され、
(define-struct point3d (x y z))
などと宣言し、
(let [[p (point3d 1 2 3)]] ...)
などと値を生成する。ここまではいい。問題はその後で要素にアクセスする場合だ。
(draw-dot (point2d-x p) (point2d-y p) (point2d-z p))
などといちいちアクセサにデータの名前が入る。俺はこれが嫌でたまらず、schemeはリスト処理のみに使うと決めて今までやってきた。
しかし先日散歩をしていたところ、突然ピコーンと閃いたのだ。haskell先生のcase文みたくパターンマッチングで要素にアクセスすればいいんじゃね?
(mylet [[[x y z] (point3d 1 2 3)]]
(draw-dot x y z))
当初自分でマクロを書くつもりだったのだが、すでに同じことを誰かがやっているに違いないと思い探してみたところ、やっぱりありまちた。match-letとゆうのが。
match-let
結構メジャーなマクロらしくいろんなschemeで採用されているようだが、俺が愛用するchicken schemeでは以下のようにモジュールを追加しなければならない。
chicken-install matchable
rlwrap csi -R matchable
話それるが、chicken schemeは目的の関数やマクロを使うのに何のモジュールが必要なのかいまいち分かりづらい気がする。チキンだから仕方ないのか。
match-letの使い方は簡単。
(define (point3d x y z)
(vector 'point3d x y z))
などとコンストラクタがある時、
(match-let [[#(_ x y z) (point3d 1 2 3)]]
(draw-dot x y z))
などとパターンマッチングできる。言わずもがなだが、アンダーバーは任意の要素にマッチする。値を読み飛ばすのに用いるのだ。
またパターンマッチングは入れ子にすることもできるのであって、
(define (polygon3d p1 p2 p3)
(vector 'polygon3d p1 p2 p3))
(match-let* [[p1 (point3d 1 2 3)]
[p2 (point3d 4 5 6)]
[p3 (point3d 7 8 9)]
[#(_ #(_ x1 y1 z1)
#(_ x2 y2 z2)
#(_ x3 y3 z3)) (polygon3d p1 p2 p3)]]
(draw-polygon x1 y1 z1 x2 y2 z2 x3 y3 z3))
うーんいいんじゃないかこれは?
いや、むしろすごくいい。というのもpythonやらrubyやらの動的型付け言語で複合データの要素にアクセスする時、ハッシュやらなんやらでシンボルの探索が行われるはずだからだ。肝の部分なので最適化されているにしても、若干のコストは免れ得ないだろう。上述のパターンマッチングではそれがない。ないと思う。
match
もちろん普通のマッチングも用意されているのであって、
(match (point3d 1 2 3)
(#('point3d x y z)
(draw-dot x y z))
(_ (error "ボケか糞が!")))
このようにこちらのマッチングでは最初の要素もチェックすべきであろう。
おわりに
まあ、そんなわけで俺的にschemeは極めて望ましい動的型付けの関数型言語となった。特にchicken schemeはコンパイルができWindows/Mac/Unixのクロスプラットフォーム開発も可能なので、今後は積極的に使おうと思っている。
上述の内容は情報強者の皆さんにとっては周知のことなのかもしれないが、少なくとも国内のscheme入門サイトでは触れられていない。なので俺と同じ見逃しをしている人は多いんじゃないかと思い投稿してみた次第である。
いじょう