見出し画像

Retro-gaming and so on

RE: Racketで何か・・009 連想リスト・Filter・Letrec

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

describe-path 関数がちゃんと動くか実験、が、動かず!



なんで? living-roomは繋がりが複数あるので駄目なのか?と、atticでチェックしたけど駄目。色々と試していると

まずはatticをクオートしてねぇからじゃねぇのかな・・・・・・。

それと、Land of Lispの実行例

> (describe-path '(garden west door))
'(there is a door going west from here)
>

に合わせて考えると、*edges*の定義が

(define *edges* '((living-room (garden west door)
           (attic upstairs ladder))
        (garden (living-room east door))
        (attic (living-room downstairs ladder))))

の場合、

  1. (assoc 'attic *edges*)(あるいは(assq 'attic *edges*))が返すのはあくまで'(attic (living-room downstairs ladder))であり(living-room downstairs ladder)ではない。
  2. 従って、目的のリストを引数に得るには(describe-path (cadr (assoc 'attic *edges*)))(あるいは(describe-path (cadr (assq 'attic *edges*))))でないとならない。

っつーわけで、この辺が正解かな。
いずれにせよ、Pythonの辞書型と違い、「連想リスト」はキーを含むリストを丸ごと返してくるので、最初は慣れないと凡ミスを行います。
しかもLispも、「ハッシュテーブル」(Pythonの辞書型にあたる)を使えばキー「じゃない部分」を返してくんだよな〜。
悩ましい。
でも取り敢えず、そういうモンだと覚えておいて下さい。

え?Lambda式で書かんといかんてこと? じゃあまあダメ元でやってみるか・・まあどうせエラーが出るんでしょ?


って、動いたーっ!!

おめでとう!

ちなみに、それが「正確な」ANSI Common LispからSchemeへの翻訳だ。
でも大方のSchemerは手癖で

(define (objects-at loc objs obj-loc)
 (define (is-at obj)
  (eq? (cadr (assoc obj obj-loc)) loc))
   (filter is-at objs))

って書くとは思う。星田さんが検索したとおりだな。
そして、

でも・・なんかやっぱり普通にdefineで書けるって納得がいかんなぁ・・とは思ってたんですよね。

って疑惑は当然だと思う。
しかし、現実はSchemerはほぼ100%「普通にdefineで書いちゃって」むしろletrecの方の出番がない。
何故なんだ、って事だ。
ここで逆に考えてみる。
果たして、ANSI Common Lispでこういう

CL-USER> (defun objects-at (loc objs obj-loc)
      (defun is-at (obj)
       (eq (cadr (assoc obj obj-loc)) loc))
        (remove-if-not #'is-at objs))
OBJECTS-AT
CL-USER>

関数を書いて良いのか、と言う。
言い換えると、Schemeの真似して「internal defun」なんつー書き方はOKなのか。
ちなみに、上のように書いても、Land of Lispの書いてるサンプルのように走る事は走るんだ。

CL-USER> (objects-at 'living-room *objects* *object-locations*)
(WHISKEY BUCKET)
CL-USER>

うん、一見問題がないように見える。
しかし、この二つは重大な違いがあるの。「計算結果を得る」のと違う意味で。
Schemeでinternal defineを使ったバージョンをもう一度見てみよう。

(define (objects-at loc objs obj-loc)
 (define (is-at obj)
  (eq? (cadr (assoc obj obj-loc)) loc))
 (filter is-at objs))

問題は内側にあるローカル関数、is-atが外側から見えるのかどうか、だ。
当然、Schemeだと、objects-atの内側にis-atが書かれてるんで、字句的に見てもis-atは外から見えるはずがありません、ってのがレキシカル(まさしく「字句的」)な結論となる。「書いた通りになるはず」ってのが前提なんだよな。
実際、

> is-at
. . is-at: undefined;
cannot reference an identifier before its definition
>

とエラーを喰らうだろう。
ところがANSI Common Lispだと違った結果になる。

CL-USER> (symbol-function 'is-at)
#<CLOSURE IS-AT>
CL-USER>

なんと大域環境上でis-atは丸見えなのである。つまり、is-atはローカル関数の要件を満たしていない
これは非常に重要なdefunの性質なの。つまり、defunはSchemeのdefineと違って大域的にシンボルに関数をセットする役目を担っていて、レキシカルな意味でどこで使われようと大域の環境相手に仕事をするんだ。
だからこそANSI Common Lispではローカル環境用の関数定義機構がまた別に必要になった、ってわけ。そいつがlabelsの役目だ。
言い換えると、Schemeはどこに何を置くか、でフツーの言語で言う「機能」の役目が変わるんだな。「レキシカル」ってのはそういう意味。Schemeはレキシカル性に準じてるんだけど、生憎ANSI Common Lispはそうじゃない。ANSI Common Lispは都合によってレキシカル性は破られる。defunはその特徴的な例の一つだと思う。
つまり、本当の事を言うと、レキシカル性でのルールで一貫してるSchemeにはletrecが必要ない、って言い方も出来るんだわ。実は要らんのだよね。
ただ、元々Schemeが生まれた時に、古典的な名前のlabels、ってのがその頃あって、ある意味後方互換性の為にletrecって名前を変えて生き残ってる・・・って考えてまぁエエんちゃうんかしらん。

あと、最後に。

remove-if-notって名前でnot入れないといかんと思ったけど、偽のものを消すってことだから正しいものを残すんだからfilterそのままで良いのか・・

ぶっちゃけ、「言語によってfilterは違う」(笑)。
そうなんだよ、「濾す為に」filterを使うのか「すり抜けさせる為に」filterを使うのか、言語によって違う、って言って良いと思う。
この名称はカッコいいんだけど、同時に悩ましいんだよな。「どっちのfilterなんだ?」って(笑)。
  • Xでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

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

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