Scheme修行 第14章 名前をつけましょう(その3)
式の値を無視して、複数の式を評価するケースを習います。関数値としてはletccにジャンプする事で返すので、途中の段階では関数値を返す必要がありません。
ノート:
leftmost
leftmostを思い出しましたか。
は話の繋がりが分からないが、原文は『Do you recall leftmost?』なので単に「leftmostを覚えていますか」と尋ねていると思う。
このleftmostの定義の問題点は(car l)がアトムだと分かってから、その値をaと名付けてまたアトムかどうかチェックしてleftmost (car l)の値とし、それにまたaと名付けてアトムかどうかチェックし…と木構造の階層を最上階まで巻き戻さなければならない点。letcc(call-with-current-continuation)を使って一気に巻き戻せる。
(else (let () (lm (car l) out) (lm (cdr l) out)))
の部分。letの値部分は複数の式を並べられる。一つ目の値が決まると(評価が終わると)二つ目が評価される。letは手続き型プログラムが書ける。
ここでは(lm (car l) out)を評価して戻って来ると言う事は「アトムが見つからずにoutが呼ばれなかった」事を意味する。なので元の定義
(let ((a (leftmost (car l)))) (cond ((atom? a) a) (else (leftmost (cdr l)))))
のelseに入るのと同じ挙動となる。
leftmost4として定義して動作確認をする。
> (let ((l '(((a)) b (c)))) (leftmost4 l)) a > (let ((l '((()) b (c)))) (leftmost4 l)) b >
二番目の例ではletの二番目の行に入った筈。
rember*
最初のスケッチの注意点は
- lが空リストの場合、再帰呼び出しから戻らずにシンボルnoを以て脱出する事
- 既に脱出する関数ohが与えられているにもかかわらず、elseに入ると再びletccでohを定義している点。
一度もelseに入らずにohが呼ばれた時には外部のletccの続きへ飛び、elseに入った場合には最後に設定されたletccの続きに飛ぶ。またohが呼ばれた場合にはその値は使われない。なので単にシンボルnoを返している。
letccが返す値がアトムと言う事はシンボルnoが返って来たと言う意味。すなわちaが見つからずリストの末尾まで到達したと言う事。
letccが返す値がアトムでない場合は、aが取り除かれたリストが返って来た事を意味する。その場合の(rm a (car l) 0)は(car l)にはaが含まれている事が分かっているので脱出する関数は使われない事が保証され、単に0を渡している。
rember1*では与えられたリストに所望のアトムが含まれていない場合の対処が必要。
letccを使ったrember1*をrember1-4*として定義し、動作確認をする。
> (let ((a 'salad) (l '((Swedish rye) (Friench (mustard salad turkey)) salad))) (rember1-4* a l)) ((swedish rye) (friench (mustard turkey)) salad) > (let ((a 'meat) (l '((pasta meat) pasta (noodles meat sauce) meat tomatoes))) (rember1-4* a l)) ((pasta) pasta (noodles meat sauce) meat tomatoes) > (let ((a 'noodles) (l '((food) more (food)))) (rember1-4* a l)) ((food) more (food)) >
ただこの定義はnoが返って来ている事をチェックするのにアトムかどうかで調べると言う点に於いてはあまり良いプログラムとは思えない。途中で出て来る脱出関数として0を充てるのも良くはないが、こちらは結果をnew-carに割り当てる事で呼び出さずに済んだので隠れてしまった。
tryの構文については恐らく自前でマクロを組まないと実現出来ない。