見出し画像

Retro-gaming and so on

RE: Racketで何か動くものを作れるかな?006 反省編

星田さんの記事に対するコメント。

まずここなんですけど「見つかった場合に値を返すようにしてれば見つからなかった場合はエラーでも返って来て止まるのではw?」とか思ってました(^_^;)

いや、実の事を言うと「考え方としては正しい」です。
その通りだ。
ただし、基本的に、「プログラムが止まる」のではなく、プログラムが動いてる「言語処理系自体が止まっちまう」ってのが厄介なの。

「考え方は正しい」、でも「プログラム自体は止めたくない」と言う場合どうするのか、ってのが例外処理、ってわけ。
ちょっとね、例外処理ってのも説明がややこしいんだけど、星田さんが思ってるような事を実現する為に例外処理、ってのが存在してんだ、ってのがあるんだ。
逆に言うと、星田さんみたいな勘の良い人じゃないと、最初「例外処理」って言われて学び始めた時、一体何のためにこれがあるんだかサッパリ分からんのね。

取り敢えず、一旦、Pythonでの例外処理を軽く見てみた方が良いと思う。
んで、例えば「見つからなかった時」ってのはベクタの範囲を超えるわけだよね。
簡単な例で言うと次のようなエラーになる。

> (vector-ref (vector 3 7 9) 4)
. . vector-ref: index is out of range
index: 4
valid range: [0, 2]
vector: '#(3 7 9)
>

「長さ3のベクタで4番目の要素を見る」ってのは無理ゲーだ。
当然エラーが返ってくるんだけど。
こういうのをC言語やPascalみたいな原始的な言語じゃないモダンな言語では「例外が投げられた」と呼称する。
そして例外処理、とは「例外を捕まえる/キャッチする」して「何もなかったように」処理を継続するための機能。平たく言うと、エラーがあったのを「無かったように」無視して作業を継続させるための機能だ。
んで、ここからちと厄介な話。
Schemeと言う仕様ではR6RSで例外処理が正式に仕様に入ってR7RS-smallでも例外処理は定義されている。
問題は、だ。Racketは既にSchemeではない。つまり、Racketには独自の例外処理の機構は付いてるんだけど、それはSchemeの機能でもないし、「文法」(と言えるのか・笑?)が他のScheme処理系とは共通してない(※1)。

Racketでは例外処理はwith-handlersと言うマクロを用いて行います。コイツが「例外」を投げられた時にそれをキャッチして処理を行う。
そして単純な使い方としては、大雑把にexn:failと言う大枠の例外を掴まえる事とする。

> (with-handlers ((exn:fail?
       (lambda (exn) "いやんばか~ん んふ〜ん そこはお外なの")))
  (vector-ref (vector 3 7 9) 4)) ;; 長さ3のベクタで要素番号4はあり得ない
"いやんばか~ん んふ〜ん そこはお外なの" ; エラーが起きても構わない
>

つまり、「わざと関数dsvがエラーを返しても構わない」ように設計した場合、関数dsvを「呼び出す側」にwith-handlersを仕込めば、エラーが起きてもプログラムを止めずに対処可能だ、ってわけ(※2)。

エラーの種類、ってのも自分で定義可能だし、もうちょっと細かくエラーを判定したい、って向けにRacketではいくつか基本的な組み込みエラーの種類が用意されている
まぁでも慣れるまではexn:failだけ使ってても暫くは構わないでしょう(※3)。

分かったような分からないようなw けど `(,x,y)でリスト形式で出力できるってのは覚えておこう

そう、事実上、(list a b) = `(,a ,b)かな。
ちなみに、`はバッククオート(あるいはクワジクオート)って呼ぶんだけど、JISキーボードでは@キーのトコにある。Shift-@ => `だな。
文章打ったりする時は使わんからパッと見だと気づきづらいかもしんない。
ただし、ANSI Common Lispでのマクロを書く時には大量に使います。
Scheme/Racketだとlistの省略記法程度にしか使わんけどね。

なお、一応カノニカルな形式としてリストを返すようにしてみたんだけど、元々星田さんが考えてたように、返り値をimproper listにしてもいいと思う。
つまり`(,i ,j)が返り値じゃなくって(cons i j)を返り値にしてもいいと思う。

出たか継続・・以前は曖昧で放置してたから結局やることになるんですね(^_^;) まあでも大域脱出ってところに絞ったら使い方は覚えられそうかな

そう、大域脱出、ってトコだけに絞って下さい。
事実上、例えばPythonで言うbreakとか。その程度の使い方、で構わん、と言う事です。
Schemeのdoもそうなんだけど、やたら強力な機構の為、最初から全部理解しようと思わん事。
慣れたらPythonのbreakなんか問題外の強力な事が出来るようになる、と。
それだけですね。

;; call/cc を使った大域脱出のフォーマット(形式)

(define (関数名 ...... )
 (call/cc      ;; 必ず二行目に call/cc を置く
  (lambda (継続名) ;; 継続名はbreakでもreturnでもcontでもccでもお好きなように
   関数本体........
   
   (継続名 引数) ;; なんかの条件があった時、「結果」を引数として継続名に渡せば大域脱出を行う

  )))

ちとPythonのbreakとかに比べると(call/cc (lambda (...ってのは記述が長い、って思うかもしれないけど、そのお陰でフツーの言語じゃあ考えられないような事が色々出来る、ってのが「継続」のメチャクチャなトコなのです。

※1: R5RSの時点ではSRFI-34で定義されてた例外処理しか存在しなかった。
Schemeの現時点での仕様ではSRFI-34の流れを汲んだguardと呼ばれる例外処理マクロが定義されている。
が、もちろんこれはRacketには含まれていない。

※2: ちなみに、SchemeだろうとRacketだろうと、例外処理機構は基本的には継続、つまりcall/ccを使って実装されている。

※3: ちなみに、「例外処理」はJavaで有名になった、と思っていいが、実は言語仕様に殆ど一番最初に取り込んだのが、例によってANSI Common Lispである。
Javaの父、ジム・ゴスリンFSFリチャード・ストールマンに批判されてるが、かと言って別にLispに暗いわけでもなく、Javaの設計時にANSI Common Lispの例外処理を借りてきても不思議ではないのである。
  • Xでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

最近の「RE: Racketで何か」カテゴリーもっと見る

最近の記事
バックナンバー
人気記事