Hatena::Groupcadr

わだばLisperになる このページをアンテナに追加 RSSフィード

2004 | 12 |
2005 | 01 | 02 | 07 | 10 | 11 |
2006 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 11 |

2007-12-13

CLOSチュートリアル (7)

| 17:26 | CLOSチュートリアル (7) - わだばLisperになる を含むブックマーク はてなブックマーク - CLOSチュートリアル (7) - わだばLisperになる

Common Lisp クックブックさんのところのCLOSのチュートリアルに新しい練習問題(CLOSチュートリアル - 4.6. 修飾子とメソッド結合)が出たので挑戦!。

問題.1

initialize-instance に :after メソッドを追加し、すべての aardvark をイギリスのケンブリッジ(Cambridge, England)から来たことにします。もう一つ、次の関係が成り立たなくなるようなメソッド(どんな修飾子をつけますか?)を追加しなさい。

解答.1

とりあえず、initialize-instanceと:afterの組み合わせで作成してみました。

(defmethod initialize-instance :after ((aardvark aardvark) &rest init-args)
  (declare (ignore init-args))
  (setf (slot-value aardvark 'comes-from) "Cambridge, England"))

animalクラスでcomes-fromは:readに指定されているので、(setf (comes-from aardvark) "Cambridge, England")はできないので、スロット変数を直に指定してちょっと強引に書き換え。これで良いんだろうか。もっと良い方法があるような…。

:accessorだったなら

(defmethod initialize-instance :after ((aardvark aardvark) &rest init-args)
  (declare (ignore init-args))
  (setf (comes-from aardvark) "Cambridge, England"))

でいけます。

;; 動作
(setf zot (make-instance 'aardvark :legs 4 :comes-from "Brittany"))

(comes-from zot)
;=> "Cambridge, England"

それで次は

(make-instance 'cannibal :diet (make-instance 'cannibal))

が成立しないようなメソッドということですが、とりあえず、cannibalクラスを定義して、

(defclass cannibal (mammal) ())

(defmethod initialize-instance :before ((cannibal cannibal) &rest init-args)
  (when (eq (class-of cannibal) (class-of (getf init-args :diet)))
    (error "Cannibal!, Cannibal!")))

と:beforeを使ってdiet引数に同じクラスが渡された場合にerrorになるようにメソッドを追加してみました。しかし、これで良いんだろうか。

;; 動作
(setq cannibal (make-instance 'cannibal :diet (make-instance 'cannibal)))
;=> エラー "Cannibal, Cannibal"

(setq cannibal (make-instance 'cannibal :diet "foo"))
;=> #<CANNIBAL #x30004393E88D>

問題.2

initialize-instance は、他のオブジェクトシステムのコンストラクタを強化したものと見ることもできます。ただし、CLOSにはデストラクタがありません。デストラクタがないのは問題ですか?

解答.2

まず、デストラクタがなんだか良く分からないので、とりあえず、Wikipediaで調べてみたところ、コンストラクタの逆で後始末をするものということで、「Javaではデストラクタは存在せず、近い機能に自動ガベージコレクションによって機能するファイナライザがある。」ってことなので、Common LispにもGCあることだし、GC任せってことでしょうか。

Practical Common Lisp (5)

| 13:58 | Practical Common Lisp (5) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (5) - わだばLisperになる

引き続きPractical Common Lisp 3. Practical: A Simple Databaseを読んでみています。どんどんペースが落ちてきました…。

Saving and Loading the Database

とりあえず出てきたコードを写経(暗記して再現)して感想をメモ
(defun save-db (filename)
  (with-open-file (output filename
			  :direction :output
			  :if-exists :supersede)
    (with-standard-io-syntax 
      (print *db* output))))

できた。これで*db*の内容をファイルに書き出すんだろう。

with-standard-io-syntaxってのは初めて使った。

CLHSで確認してみたところ、これに囲まれた入出力は標準化される(CLHSに一覧あり)ので、処理系の違いを気にする必要がないとのこと。

出力されたデータは、処理系に依存しないものにしたいので、こうしているのだろう。

  • with-standard-io-syntaxを試してみる。
(let ((*print-case* :downcase))
  (print 'foo))
;=>fooと印字される

(let ((*print-case* :downcase))
  (with-standard-io-syntax
    (print 'foo)))
;=>FOOと印字される(標準では:upcaseなため)
(defun load-db (filename)
  (with-open-file (in filename :direction :input)
    (with-standard-io-syntax
      (setf *db* (read in)))))

できたけど、:direction :inputは不要だった。デフォルトではinputになるからだろう。

以上を踏まえて本文を読んでみる。
  • save-db
    • WITH-OPEN-FILEの解説
      • ファイルを開いてストリームと結びつけ後始末もしてくれるマクロ。キーワード引数が色々取れて、入出力の向き、ファイルが存在しない場合の動作等、色々指定可能。
    • PRINTの説明。
      • FORMATと違って、READで読み込める形式で出力することになっている。
    • WITH-STANDARD-IO-SYNTAXの説明。
      • 写経時の考察で大体合っていて、可搬性を高めるためのに使用している。
  • load-db
    • WITH-OPEN-FILEはデフォルトでは:inputを指定した動作となるので省略可能。
    • SETFの説明。
      • *db*の内容を上書きしている。