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 |

2008-05-08

CADRでFLAVORS

| 16:10 | CADRでFLAVORS - わだばLisperになる を含むブックマーク はてなブックマーク - CADRでFLAVORS - わだばLisperになる

CADRエミュレータで何かしようと思いつつも前回からまた一ヶ月経ってしまいました。

最近CLOSのような、LISP上でのOOPになんとなく興味があるのですが、CADRには、CLOS以前のflavorsというOOPの枠組がありました。

よくRubyなどで、mixinとかいう用語が使われますが、この用語は、確かflavorsに由来していたと思います。

アイスクリームのトッピングに由来した用語なのですが、flavorsという名前自体、そのトッピングのことを指してるみたいです。

とりあえず、何から始めたら良いか良く分からないので、ポール・グレアム著のANSI CLのCLOSの章をなぞってみることにしました。

とりあえず、構造体とクラスを比較してみる、という内容なのですが、defstructの細かいところが、CLとLisp Machine Lispで違っていたりして、それもまた乙です。

とりあえず、CLの構造体だとこんな感じです。

;; CL
(defstruct rectangle
  height
  width)

(defstruct circle
  radius)

(defun area (x)
  (cond ((rectangle-p x)
         (* (rectangle-height x) (rectangle-width x)))
        ((circle-p x)
         (* pi (expt (circle-radius x) 2)))))

(let ((r (make-rectangle)))
  (setf (rectangle-height r) 2
        (rectangle-width r) 3)
  (area r))
;=> 6

自動で、rectangle-widthのようなアクセサを作ってくれて、rectangle-pのような述語も作成してくれます。

次にLisp Machine Lispでの構造体でのバージョンです。

;; Lisp Machine Lisp
(defstruct (rectangle :named)
  rectangle-height
  rectangle-width)

;; アクセッサはフルネームじゃないと駄目
;; namedにしないと、
;; (typep obj 'rectangle)とか効かないので、
;; namedで作らないとrectangle-pのようなものが作れないらしい。

(defstruct (circle :named)
  circle-radius)

;; piは定数ではないので定義
(defconst pi 3.141592653589793238)

;; 自動で、述語を作ってくれないので自作
(defmacro make-pred (name)
  `(defun ,(intern (string-append (string name) "-P")) (obj)
     (typep obj ',name)))

(make-pred rectangle)
(make-pred circle)

(defun area (x)
  (cond ((rectangle-p x)
         (* (rectangle-height x) (rectangle-width x)))
        ((circle-p x)
         (* pi (expt (circle-radius x) 2)))))

(let ((r (make-rectangle)))
  (setf (rectangle-height r) 2)
  (setf (rectangle-width r) 3)
  (area r))
;=> 6

そして、Lisp Machine LispのFlavorsで

;; Lisp Machine Lisp
(defflavor rectangle (height width) ()     
  :gettable-instance-variables
  :settable-instance-variables)

(defflavor circle (radius) ()
  :gettable-instance-variables
  :settable-instance-variables)

(defmethod (rectangle :area) ()
  (* (funcall-self ':height) (funcall-self ':width)))

(defmethod (circle :area) ()
  (* pi (expt (funcall-self ':radius) 2)))

(let ((r (make-instance 'rectangle)))
  (<<-- r
        (':set-height 2)
        (':set-width 3)
        (':area)))
;=> 6

ざっとした説明ですが、まず、キーワードに一々クオートが付いています。

これは、Lisp Machine Lispでは、CLのようにキーワードパッケージがあって、そのシンボルの評価結果が自分自身となるようなものではなく、ただの目印的なものなので、クオートが必要になります。

次に、defflavorですが、これは、CLOSのdefclassに相当します。

書式は、

(defflavor クラス(フレイバー)名 (スーパークラス)
  ...)

のような感じで、

:gettable-instance-variables

:settable-instance-variables

:inittable-instance

は値を取得/設定/初期化できるようにする指示で、デフォルト値も設定できます。

とりあえず、フレーバーを作ったら、

(setq r (make-instance 'rectangle))

(<- r ':set-width 3)

のようにして、値を設定できたりします。

rというオブジェクトに:set-widthと3というメッセージを送るのですが、

(funcall r ':set-width 3)

でも良かったりするみたいです。

ちなみに<-は後に、sendという名前になったようです。

set-〜というところは、
settable-instance-variablesをすると自動的に:set-〜で値を設定することができるようになります。

それで、defmethodですが、

(defmethod (フレーバー キー?) (引数)
  ...)

となり、CLのdefmethodとはちょっと違っています。

呼び出し方ですが、

(<- r ':area)

のようになるようです。

funcall-selfですが、defmethod内では自身を指すselfという変数が使えるので、これは、(<- self ...mesg)と同じことのようです。

CLOSのように(area r)とは書けないみたいでなんですが、初期のFlavorsの試行錯誤の後、「関数呼び出しの形とメソッド呼び出しの形を一緒にしたら良いんじゃないか?」というアイディアが現われて形が統合されたらしいです。

<<--は、(progn (<- ..) (<- ..))に展開されるマクロで上の例は、

(let ((r (make-instance 'rectangle)))
  (<- r ':set-height 2)
  (<- r ':set-width 3)
  (<- r ':area))

と同じです。

まとめ

という感じで、自分も全然分かっていないので、解説らしい解説にもなっていないのですが、色々いじって遊んでみようと思っています!

2008-04-03

またふらふらとCADRでなにかする

| 19:08 | またふらふらとCADRでなにかする - わだばLisperになる を含むブックマーク はてなブックマーク - またふらふらとCADRでなにかする - わだばLisperになる

メインの環境を64bit Linuxにしてから、CADRエミュレータが上手く動かなくなっていたので放置していたのですが、Ubuntuの場合ia32-libsをインストールすれば、面倒もなく32bit版で動くことが分かったので、またいじり始めてみることにしました。

以前のエントリでは、ダウンロードから導入、ちょっとした開発環境の説明を書きました(CADRカテゴリーに纏めてあります)が、

今年は、もう一歩踏み込んでみたいところ。

目標としては、Flavorsなどを使ってプログラミングするところ位まで探究できたら良いなと思っています。

配布されているMIT CADRエミュレータの場合、初期のマニュアルに説明のある機能が無かったりしますので、結構初期のLisp Machine Lispのようです。

とりあえず今日は久し振りに使ってみるということで、前々から試してみたかった、ZMACSへのコマンドの追加を試してみたいと思います。

ZMACSは、Lispマシンで動くEMACSで、オリジナルのEMACSとほとんど同時期の1976年に開発がスタートしたようです。

最初は、EINE(Eine Is Not EMACS)という名前で、その後、ZWEI(Zwei Was Eine Initially)という名前になり、Zmacsへ、という流れになります。

Lispマシンは全面的にLispで記述されているので、当然その上で動くZMACSもLispで記述されています。LISPで記述されているということは、最初からLISPで拡張可能なわけで、恐らくLISPで拡張可能なエディタとしては、ZMACSが一番最初のものなのではないでしょうか。Multics EMACSは、1978年で、Multics EMACSの作者もZMACSの方が先に存在してたと言ってましたし…。

それはさておきZMACSの拡張コマンドですが、DEFCOMという対話操作拡張用のマクロが用意されているのでそれを使ってみます。

作るコマンドですが、自分は、普段ダブルクオートをM-"のキーバインドで、対で入力しています。

EMACSでは、幾つかこの様な入力方法を実現する方法があるのですが、ZWEIにもCOM-MAKE-()という、括弧を対で入力する関数があるので、これをちょっといじって作ってみました。

この関数は数引数を受けとることにより、任意の式をまたいで囲むことができます。

(pkg-bind "zwei"
  (DEFCOM COM-MAKE-/"/" "Insert matching delimiters, putting point between them.
With an argument, puts that many s-exprs within the new /"/"." ()
    (LET ((OPEN #/") (CLOSE #/")
          (MOVER 'FORWARD-SEXP) (POINT (POINT)))
      (DO ((CH (LDB %%CH-CHAR *LAST-COMMAND-CHAR*))
           (L *MATCHING-DELIMITER-LIST* (CDR L)))
          ((NULL L))
        (COND ((OR (= CH (CAAR L)) (= CH (CADAR L)))
               (SETQ OPEN (CAAR L) CLOSE (CADAR L) MOVER (CADDAR L))
               (RETURN T))))
      (LET ((BP (IF *NUMERIC-ARG-P*
                    (OR (IF (EQ MOVER 'FORWARD-SEXP)
                            (FORWARD-SEXP POINT *NUMERIC-ARG* NIL 0 NIL T T)    ;No UP
                          (FUNCALL MOVER POINT *NUMERIC-ARG*))
                        (BARF))
                  POINT)))
        (AND (MINUSP *NUMERIC-ARG*) (PSETQ BP POINT POINT BP))
        (INSERT BP (IN-CURRENT-FONT CLOSE))
        (INSERT-MOVING POINT (IN-CURRENT-FONT OPEN))
        DIS-TEXT)))
  )

ちょっとした説明

  • EMACSとZMACSの違い

上記のコードを眺めるとGNU Emacsとは結構違っていることが分かると思います。Hemlockや、LispWorksのエディタ、Climacs等これに近いのですが、恐らく、ZMACSから影響を受けていて、ZMACS系とも呼べると思います。

  • pkg-bind
    • ボディ部は指定したパッケージ内で評価されます。上記の場合、"ZWEI"パッケージ内で評価されています。Lispマシンの場合、ファイルの一行目にパッケージを記述することで、指定することができるのですが、この方法を発見したので試しに使ってみています。

それで、定義した関数をどうっやってキーバインドに割り付けるかですが、

割り付けには、SET-COMTABやSET-COMTAB-RETURN-UNDOを使用します。

自分は、初期化ファイルで設定するので、ログアウトしたらアンドゥされるように、SET-COMTAB-RETURN-UNDOを使ってみています。

(login-eval
 zwei:(set-comtab-return-undo *standard-comtab* '(#^C/" com-make-/"/"))) ;^Cはコントロール文字

という風にホームディレクトリのlispm.initに記述すれば、M-"でcom-make-""が呼び出されるようにログイン時に初期化されます。

ちなみに、パッケージの指定方法が不思議な感じになっていますが、こういう風にも書けたみたいです。

zwei:(set-comtab-return-undo *standard-comtab* '(#^C/" com-make-/"/"))
=>
(zwei:set-comtab-return-undo zwei:*standard-comtab* '(#^C/" zwei:com-make-/"/"))

ということのようなのですが詳細はまだ、良く分かっていません。pkg-bindの略記方法なのかもしれませんが、便利といえば便利なような。

という感じで、ふらふらとしばらくMIT CADRを使ってみようと思います。