星田さんの記事に対するコメント。
今回は先に、「プログラミングの基礎」の第18章に対する回答例をPaizaの方に挙げておこう。
なお、OCamlの静的型付け言語の縛りによる独特のデータ型に対しては構造体で対応する事にしてる。
まずは軽くジャブ。
ex18-1
![](https://blogimg.goo.ne.jp/user_image/44/c4/8db2722f6169016e0676ba08a2fbb60c.png)
![](https://blogimg.goo.ne.jp/user_image/7b/d9/9bae20c7c1c81906457e49c23827d1ac.png)
OCamlのオプション型を使って解を仕分ける問題。
これは今まで見てきた通り、本質的にはSchemeなんかのLisp系語族、及び動的型付け言語には関係ない話題だ。OCamlは強い型付け言語な為、Lispや他の動的型付け言語だったらアッサリと書ける問題を「わざわざキテレツな型を作って」対応せなアカン、ってだけの話になる。
これを見る限り、静的型付け言語では「褒められる」解に見えても動的型付け言語のユーザーからは
「なんて面妖な・・・・・・。」
と言う感想しか得られないだろう。
いずれにせよ、直訳では、ストレートにdefine-record-typeで型を作って凌ぐ事になると思う。
続いてもジャブだ。
ex18-2
![](https://blogimg.goo.ne.jp/user_image/5e/23/7d4b50cde6819e38ff689a65be4807ab.png)
これもLisp的観点から言うと、単なる再帰の問題に過ぎない。OCamlでは「Option型の練習」になるが、その辺はコッチ側からすると割に「どーでも良い」話題に見える。
なお、この問題でのNoneとSomeはprice.scmで定義している。
![](https://blogimg.goo.ne.jp/user_image/7b/9d/97b0ddacc7fa96bbfa5b7a30edcc96fb.png)
ex18-3
![](https://blogimg.goo.ne.jp/user_image/1b/75/dfc03550544a71f0999a878b4cf9759f.png)
Lispで言うassocを作ろうぜ、と言う問題。
ユーティリティの項目でも見たが、実はこの関数、現在では色々な実装法があるわけだが、オーソドックスに教科書的には
![](https://blogimg.goo.ne.jp/user_image/01/df/cdaeb4042c152a670bc3d0e56a0ca434.png)
と書く関数だ。
ただし、注意点がある。次の二点だ。
- Schemeではobjをキーとするリストが見つからなかった場合は単純に#fを返すが、まずはこの問題の性質上、「見つからない」と言う例外を投げるようになっている。
- Lisp系のassocはobjをキーとするリストが見つかった場合、そのリストを丸ごと返すのが通例だが、ここの問題の場合、キーと結び付けられた値を返している。
問題の構成としては悪くない。むしろ「良く出来てる」と言ってイイんだけど、同時にOCamlの「静的型付け」のデメリットも実はよく分かる問題となっている。
1. と2. が示すOCamlの弱点とは以下のような事だ。
Lisp系のassocは状況によって真偽値、つまりブール型と言うデータ型かあるいはリスト型と言うデータ型を返す。事実上、「返す型に制限がない」。これは静的型付け言語では認められない動作なんだよ。
従って、OCamlの「静的型付け」だと、「キーに対応する値が見つからない」場合は、まるで本体の「動作」と関係がないように見える「例外を投げた」方がラクになる、と言う背景がある。
ついでに多相の型を受け取って多相の型を返す方が多相の型を受け取ってリストを返すよりも「受け取った型から返す型への流れ」で考えると自然なんだろう。よってリストでは返さない方がマシって判断になるんじゃないか。
これは「プログラミングの基礎」の著者である浅井センセの発想と言うより、OCamlのライブラリ関数であるassocの挙動を参考にした為だと思う。OCaml組み込みのassocは、キーが見つからない場合はNot_found例外を投げ、見つかった場合はリストではなく値を返す。
そのため、Lispのassocのつもりで使うととんでもないトコでハマるだろう(実際ハマってたし・笑)
とまぁ、そこを押さえておいて、Schemeでは仕様上、Not_found例外なんて無いし(と言うより、Pythonみたいに固有の例外は定義されていない)、あるScheme処理系に於いてあったとしてもそれは実装依存だ。
よってraiseでシンボルを投げる事で対処すればいいだろう。
(raise 'Not-found)
あとは、この記事で見せたように、例外の「補足」だけに気を使えばいいだけ、だ。
ex18-5
![](https://blogimg.goo.ne.jp/user_image/23/f6/011a0e9190f77286ac7a069746b88827.png)
![](https://blogimg.goo.ne.jp/user_image/1c/6e/63d68fc400030e3932f9dc14e91dba42.png)
![](https://blogimg.goo.ne.jp/user_image/4b/43/a8e37a1b285c7936bd7e381a7d305779.png)
![](https://blogimg.goo.ne.jp/user_image/6f/66/11b16b3d56ab89837a8b71bae453385b.png)
構造的にはget-ekikan-kyori関数で例外を投げ、koushin関数で例外処理を行う、と言うのが章の目的だ。
raiseの方はまぁいいだろう。先にも書いた通り、Schemeの仕様上、「どういう例外があるか」は一般論として定義されていない。例えばPythonのように、実装によっては「何十個も」例外を用意してくれてるだろうが、取り敢えずここではどっちみちそれらは使えないんだ。
従って、実装依存の「例外」を使用しない前提だとraiseで例外を「作って」投げるしかない。
ここでは例外として'Not-foundと言うシンボルを投げる事としている。
まず、例示とかツッコみたい人は、元々guardはSRFI-34で提案されたモノなんで、その辺で例示を調べてみたらいいと思う。
R7RSでの定義は次のように書いてある。
![](https://blogimg.goo.ne.jp/user_image/72/ce/8eb7bd91ad338a57aa7e4f6168d65a27.png)
![](https://blogimg.goo.ne.jp/user_image/6f/61/346f1a29622fa6685a13e3d932a63563.png)
例示だとconditionとか、構文要素っぽい書き方になってるが、ここは単なる変数で、xだろうと何だろうといい。ここに「投げられた」例外が束縛される。
その「投げられた例外」がどんな例外なのか、と言うのを以下で判定するんだけど、この問題で即した言い方をすると、raiseでシンボル'Not-foundを投げた以上、変数condition(とかxとか任意のモノ)の中身が'Not-foundと言うシンボルかどうかを調べればいいだけだ。
つまり、このケースだと
(eq? condition 'Not-found)
が#tの時、「Not-found例外を捕捉した」と言う事になる。
これがSchemeの仕様上の「大雑把な」例外処理だ。
実装依存で例外が色々定義されてる場合は、「例外がどの例外なのか」判定する仕組みは実装依存で色々とあるだろう。いずれにせよ、変数conditionは投げられた例外を束縛して、判定自体は実装依存の・・・恐らく述語を使う事になるだろう。その述語はその実装依存なんで、リファレンスマニュアルを調べる事となる。
いずれにせよ、guardは一般論で「使い方を説明する」のはちと難易度が高くなるが、一方、仕様で「ふわっと」定められてる範疇なら、割に緩く使う事が可能だ・・・Lispに於けるシンボル判定なんざ大して込み入った話でもねぇしな。
この問題でもう一つ、ピットフォールは先にも書いたassocだ。tree版get-ekikan-kyori関数はassocを内包してるが、ついついLisp版assocとOCaml版assocを同一視すると足下をすくわれる。いや、すくわれた(笑)。
こーゆー時、バグの原因がサッパリわからなくなったりするんで相当焦ったぞ(笑)。
もう一回繰り返すが、
- Lisp版assocはキーと値を含んだリストを返すがOCaml版assocは値しか返さない。
- Lisp版assocはキーに対応した値が無い場合、偽値を返すがOCaml版assocはNot_found例外を投げる。
と言うのがポイント。OCamlとLispの違いを消化するようにコードを改造しないとならないんだ。
よって、基本的には、Empty以外で、木に含まれるリスト(変数lst)がキーと対応する値を持ってる場合はcdrを適用、そうじゃない場合は、やっぱり例外'Not-foundを投げないとならない。
いずれにせよ、原作ではex18-3で作った「自作assoc」ではなく、OCamlの標準ライブラリでのList.assocが用いられている(それが混乱の元・笑)
残り、ちょっとした注意点としては、OCamlのコード上、eki1とkは文字列だ。OCamlの場合、ポリモーフィズムで<で文字列同士を比較出来る。
一方、Schemeの場合は、<で文字列を比較出来ない。string<?を使うように。引っかからないようにしよう。
また、これが意味するのは、ex17-12で作ったinsert_ekikan関数は、「文字列の大小」を基準にして二分木を組み上げている。
![](https://blogimg.goo.ne.jp/user_image/0e/cb/a63ca6c7e4b8ad3f8df7e9c9a5e14a50.png)
![](https://blogimg.goo.ne.jp/user_image/70/6a/c9e400d5582051df428b4c7f1a96d125.png)
ex18-7
![](https://blogimg.goo.ne.jp/user_image/59/ae/4ad51fbc01acfee4b3149dc1e7cbdcad.png)
18章最後の練習問題。クッソ軽めの問題だ。
実はこの前にex18-6っつーのもあるんだけど、OCamlと違って、Schemeのraiseは何でも投げられる。従ってexceptionっつーコンセプトも必要がねぇんだ。
よってわざわざNo_such_stationと言う例外を作る必要がない。この問題的には
(raise `(No-such-station ,r0))
と変数r0を埋め込んだリストを例外として投げればいいだけ、だ。
また、本格的な「例外」(と言うデータ型)を作る方法はあり得るんだけど、残念ながらそれはSchemeの仕様書を超えたトコにあって、各実装のマニュアルを参照せねばいけない範疇、となる。