実践Common Lisp p213-252

メモを残しておきます。また長くなってしまったので畳みます。

  • FORMATはある種の言語と言ってもいいくらい複雑な処理ができるようだ。しかしきもい。
  • DEFINE-CONDITIONによって作られるクラスのスーパークラスはSTANDARD-OBJECTではなくCONDITION。だからSLOT-VALUEではアクセスできないし、INITIALIZE-INSTANCEも使えない。:initargsと:readerと:accessorは使える。
  • なるほど。RESTART-CASEで再起動を指定することでコールスタックでの下位の関数が上位の関数にどうしますかっていうお伺いを立てに行くことができるようになるんですね。部下(下位の関数)はトラブルに対処するいくつかの選択肢を持っていて、トラブルが起きた時は一旦処理をそのままにして(戻ってきたら続きができる状態)、上司(上位の関数)にどうするか聞きにいく。上司は部下の報告を聞いて、部下にトラブルに対処する方法を指定する。もしくは部下ができない処理(RESTART-CASEのコード部分で指定していない処理)をしたい場合は上司が別の人(別の関数)を指定してさせることができる。この上司で判断できない場合(コンディション処理の辞退)ならさらに上の上司(さらに上位の関数でコンディションを受け付けるもの)にお伺いにいく。みたいな感じでしょうか。とてもよくできてると思います。あ、なんかちょっと偉そうなこと言ってしまったかも。コンディションすごいなー。なんだろう、Schemeの継続はこれをより原始的にしたものに似てるような気がするけどどうなのかな。Schemeの継続いまいち分かってないんですけどね。印象というか。
  • 堅牢なソフトウェアを書くための推奨本がp235にあります。そのうち読みたい。
  • ほとんどの関数はグローバルにDEFUNされるって書いてありますね。Schemeではブロック構造でローカルな関数を結構使ってたけど、パッケージシステムとかCLOSがあるみたいだからCommon Lispではあんまり必要ないのかな。
  • ローカルな関数はFLETとLABELSを使うと作れる。後者は再帰関数が作れる(定義内部で自身を参照できる)が前者は作れない。FLETとLABELSのフォームはSchemeの名前付きletに似てますね。
  • 無名関数の再帰はそういえばここでやりました。関係ないですけど、はてな記法の**はa name属性を勝手につけてはくれないんですね。後でつけよう。
  • DEFUNもBLOCKを使ってるからRETURNで値を返せる。


  • BLOCKの名前はレキシカルスコープを持つ。だから別の関数でRETURN-FROMしたい場合、下みたいにLAMBDAでラップして渡さなきゃいけない。

    (defun foo ()
      (format t "Entering foo~%")
      (block a
        (format t " Entering BLOCK~%")
        (bar #'(lambda () (return-from a)))
        (format t " Leaving BLOCK~%"))
      (format t "Leaving foo~%"))
    
    (defun bar (fn)
      (format t "  Entering bar~%")
      (funcall fn)
      (format t "  Leaving bar~%"))
    


  • コンディションもBLOCKとかTAGBODYとかでできるらしいです。BLOCKの名前が動的エクステントだからSchemeの継続とはまた違うんですね。でも二つの場所を行き来することができるならコンディションの基本的な部分は作ることができるんじゃないでしょうか。ということで以下のようなものを考えてみました。

    (defun foo (fn n)
      (when (< n 3)
        (format t "The n is ~d.~%" n)
        (tagbody
          a
          (format t "Entering A ~%")
          (funcall fn)
          (format t "Leaving A~%"))
        (format t "Leaving foo~%")))
    
    (defun bar (fn n)
      (let ((k n))
        (tagbody
          b
          (setf k (+ k 1))
          (format t "Entering B~%")
          (foo #'(lambda () (go b)) k)
          (format t "Leaving  B~%")))
      (format t "Leaving bar~%"))
    

    これでbarを走らせてみると

    CL-USER> (bar #'+ 0)
    Entering B
    The n is 1.
    Entering A 
    Entering B
    The n is 2.
    Entering A 
    Entering B
    Leaving  B
    Leaving bar
    NIL
    

    ということで二つの場所A,Bを行ったりきたりできます。BLOCKでも手続きで渡せばちゃんと帰ってこれるので、頑張ればコンディションシステムのマクロが作れるんじゃないでしょうか。やりませんが。

  • コンパイルとロードの関係の説明がほしかったのでうれしい。
    • LOADは上から順に評価される。だからDEFMACROとかは最初の方でやっておくべき。
    • COMPILE-FILEはコンパイル時には式を原則評価しないが、LOADと等価にするためDEFMACROやIN-PACKAGEなどは評価する。この時の評価方法をEVAL-WHENで制御することができる。
    • トップレベルフォームはFASLのLOAD時に実行コードへコンパイルされる。
      • トップレベルフォームとはコードのトップレベルに現れるもの、トップレベルPROGN、それからMACROLETやSYMBOL-MACROLETなど。
      • トップレベルのマクロフォームの展開形も。だからDEFUNも。
      • 非トップレベルフォームは明に書いてないけどトップレベルフォームでない全てのフォーム、という理解でいいのかな。
    • EVAL-WHENは自身の部分フォームのコンパイルタイミングを制御することができる特殊オペレータ。トップレベルフォームとして評価される時と非トップレベルフォームとして評価される時で挙動が違う。
    • EVAL-WHENを使うことでマクロやパッケージのある場面での利用が可能になる。


こんな感じ。ひっかかったところを書きだすとちゃんと理解できるので(それと後から見直せるので)書いてるんですが、ひっかかるところが多すぎるので長くなってしまう。もっとさーっと読みたい。です。