#:g1: frontpage

 

Zmacsで複数ストロークのコマンドを定義する

Posted 2015-03-22 05:56:32 GMT

 Zmacsでは、下記のようにzwei:*zmacs-comtab*zwei:set-comtabでキーとコマンドの対を登録して行けばキーバインドを登録することができます。

(zwei:set-comtab zwei:*zmacs-comtab*
                '(#\control-h com-rubout)

set-という名前なので、全体を上書きしてしまいそうですが、動作としては、キーバインドを新しく追加してくれます。

 さて、このように1ストロークの場合は、シンプルに対を指定すれば良いのですが、複数ストロークの場合は、どうするのか。
よく分からないのでzweiのソースを眺めてみましたが、参考になるのは、C-x なんとかという2ストロークのコマンドでした。
これはZmacsの場合、zwei:*zmacs-control-x-comtab*というサブのcomtabを定義して、それを更に上位のcomtabに登録しているようです。

 ちなみに、ZmacsとかZweiが入り乱れていますが、ZmacsはSymbolics版のZweiといった所で、ほぼZmacsもZweiも同じものです。
ややこしいのは、Symbolics以外でもZmacsと呼んだりしてしまうところと、Symbolics版のZmacsでもエディタのパッケージ名がzmacsと改められたりすることもなくzweiで通されてしまったことです。

自作コマンドに2ストロークのキーバインドを割り当てる

 さて、やり方はなんとなく分かったので、早速これを真似して、meta-G Lという2ストロークのキーバインドに、今日の日付がファイル名になったスクラッチファイルを作成する自作コマンドを割り当ててみます。

まず、コマンドですが、

;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp -*-

(defpackage scratch-files
  (:use :cl))

(in-package :scratch-files)

(defvar *base-dir* (merge-pathnames (make-pathname :directory '(:relative "SCRATCH-FILES")) (user-homedir-pathname)))

(defun type-yyyy-mm-dd (type) (multiple-value-bind (s- m- h- d m y) (get-decoded-time) (declare (ignore s- m- h-)) (format nil "~A-~4,'0D-~2,'0D-~2,'0D" type y m d)))

(defun the-file-name (type suf) (merge-pathnames (make-pathname :name (type-yyyy-mm-dd type) :type suf) *base-dir*))

;; (the-file-name "CL" "LISP") (zwei:defcom com-open-scratch-file "Open the scratch file." () (zwei:find-file (the-file-name "CL" "LISP")) zwei:dis-text)

このような定義。

comtabを定義する

このscratch-files::com-open-scratch-filemeta-G Lに割り付ける訳ですが、それにはまず、下位のcomtabを定義します。
ここでは、Emacs 24のmeta-Gのキーマップに倣って、goto-comtabとしてみます。
次に、これにキーバインドのLと対になるscratch-files::com-open-scratch-fileを登録します。
そして、この*goto-comtab*を、上位のzwei:*zmacs-comtab*に登録しますが、ここでzwei:make-extended-commandというのを挟んでやればOKのようです。

(defvar *goto-comtab*
    (zwei:make-comtab))

(zwei:set-comtab *goto-comtab* '(#\l scratch-files::com-open-scratch-file))

(zwei:set-comtab zwei:*zmacs-comtab* `(#\meta-G ,(zwei:make-extended-command *goto-comtab*)))

これだけでmeta-G Lでコマンドが実行されるようになりました。
あとは初期化ファイル等に書いておくだけです。

まとめ

 Zmacs/Zweiでは結構簡単に多段のキーバインドを定義することが可能な様子。
これで慣れ親しんだEmacsのキーバインドでZmacsを操作できて快適ですね。
とはいえ、あまりやるとZmacsらしくなくなりますが…


HTML generated by 3bmd in SBCL 1.2.9

Open Generaでtelnet/rsh

Posted 2015-03-19 04:01:32 GMT

 タイトルの通りで、それ以上のことはないのですが、Open Generaで、telnetrshが使えるようなので試してみました。

telnet

 telnetはsystemキー(Open GeneraではF1がデフォルト)に割り当てられているので、system t で接続画面になります。
登録されているホストを選択すれば、telnet可能。
今時のOS環境ではsshの利用が標準なので、telnetは無効になっていることが多いので別途インストールすることになると思います。
Open Genera側でページャーが働くところがなかなか乙です。

og-telnet

MIT CADRでは、telnetではなくsupdup (ITSで良く使われた通信プロトコル) でしたが、telnetの方が普及してしまったので、こちらが標準搭載になったのでしょう。

ちなみに、現存するITSのサイトにはsupdupでログイン可能です。

rsh

 rshの方は、いまいちはっきりしていませんが、リスナーでExecute Commandすると、rsh host -c cmdするようです。
~/.rhostsでパスワード不要の設定にしてみましたが、どうも上手く行かず。

og-rsh

rshsshに置き換えられているので、rshのサーバをインストールする必要があるでしょう。

 また、上記では、リスナーで実行していますが、ホストとのやりとりの関数は一式揃っているので、自作のLispプログラムからも利用可能です(なんでもLispで書かれているので当然ですが)。

まとめ

 Open Generaの方にtelenetすることも可能みたいなのですが、今の所その方法が分からず。
これができると、外部のEmacsから使えたりして便利な気がするので、今後探ってみたいと思っています。


HTML generated by 3bmd in Clozure Common Lisp Version 1.10 (LinuxX8664)

Symbolics.com 30周年でOpen Generaの初期化ファイル晒し

Posted 2015-03-15 14:50:01 GMT

 30年前の今日、1985-03-15日は、世界最初の.comドメインが取得された日ですが、取得した会社は、symbolicsでした。
そんなsymbolics.comですが、会社は1995年に破産。その後もメンテナンス関係の仕事は引き続き行なわれていたようですが、symbolics.com も2009年にドメイン業者に売られてしまいました。
なお、ドメインは変わりましたが、現在でも細々とSymbolics関係のものを扱っているようです。

そんなSymbolicsを偲んで本日は、自分が使っているOpen Generaの初期化ファイルを晒したいと思います。

私のOpen Generaの初期化ファイル

 初期化ファイルの構成ですが、参考になるGeneraの初期化ファイルを公開しているところは少ないのですが(当然か)、LMI-LambdaやTI-Explorerのスナップショットなどがネット上に転がっているので、参考にそれを眺めたりしました。
眺めた感想としては、lispm-init.lisp に纏めて書く人が殆どで、ディレクトリに纏めたりしている人はあまりいないようです。
一人だけ初期化用のsystemを定義して読み込んでいる人がいたので、これは良いと思ってちょっと試してみましたが、CADRや、LMI-Lambda系では、sysmtemがmake代りに使えたようですが、Symbolicsはそこから進化して複雑なものになってしまったようで、ちょっとしたタスクを纏めるような使い方はしづらくなってしまったようなので、この作戦は見送ることに。

 色々考えましたが、GitHubに置いて管理したいので、 lispm-inits というディレクトリを作成し、そこに纏めることにしました。
Open Generaでは、シンボリックリンクも問題なく追従するようなので、lispm-inits/lispm-init.lisp からホームディレクトリにリンクを張ります。

カスタマイズ内容

キーバインド

Zmacsのキーバインドは、comtabというテーブルにキーと関数を登録します。
自分は、こんな感じにしています。

(set-comtab *zmacs-comtab*
            '(#\control-m com-insert-crs
              #\control-h com-rubout
              #\meta-shift-l com-make-\(\)
              #\meta-: com-forward-up-list
              #\control-i com-indent-for-lisp
              #\control-meta-shift-h com-kill-backward-up-list
              ;; #\control-/ com-insert--
              #\meta-space com-insert--
              #\control-shift-j com-evaluate-and-insert-into-buffer
              #\control-meta-i zwei::com-complete-definition-name-rj
              ;;#\control-meta-i com-complete-definition-name
              ;;        #\control-meta-i com-list-definition-names
              )
            (make-command-alist '(com-evaluate-and-insert-into-buffer)))

  • C-m → 改行
  • C-h → バックスペース
  • C-sh-L → () を挿入
  • C-M-I → シンボルの補完(後述)
  • C-sh-J → 評価した内容をバッファに;⇒ 付きで挿入(後述)

位の所です。

便利ツール

このブログのようにLispコードを載せることが多い場合に重宝するのが、

(print (fib 20))
;>> 
;>> 6765 
;=> 6765

こんな風に評価結果をバッファに挿入してくれるユーティリティなのですが、TI-Explorerのソースコードの中の、tools/zmacs-enhancements.lisp に似たようなことをするユーティリティが定義されていたので、これを改造して使ってみることにしました。
ブログ記事を書くに限らずバッファに結果が挿入されると色々と便利ですね。

シンボルの補完

Zmacsにも com-complete-definition-name というのがあるようですが、あまり補完具合が良くありません。
SLIMEのやつを移植してみようかなとも思いましたが、Rainer Joswig氏が作っていたのがあったのを思い出したので、これを使ってみています。

スクリーンショットはこんな感じ
zmacs-completion-rj

読み込み時にパッケージの警告等がでるので、若干変更して使っています。

まとめ

symbolics.com 30周年を記念してOpen Generaの初期化ファイルを晒してみました。
Lispマシンのユーザーの初期化ファイルもコレクションしてみたいところですね。


HTML generated by 3bmd in LispWorks Personal Edition 6.1.1

SLIMEのSLDBの画面を見やすくする

Posted 2015-03-10 12:36:24 GMT

 日頃お世話になっているSLIMEですが、どうもデバッガのメッセージを把握しづらい気がしていました。
主にエラーメッセージが他の情報に埋もれている気がしたのですが、Emacsでメッセージのfaceを変更できないか調べてみた所、カスタマイズできる箇所は沢山用意されていたので、色々と変更してみました。
主な変更点としては、

  • エラーメッセージの主張が弱いので、フォントを大きくして、色もオレンジに変更
  • コンディション名は、必要な場合に確認すれば良い程度なので暗い色に変更
  • 各セクションの見出しが目立たないので緑にして下線を引いてさらに斜体に変更
  • SLDBからいじれるデバッグ用の変数を薄緑に変更
  • 全体として、リスタートの候補よりは、バックトレースの方が目立って欲しいので、バックトレース側を目立つように変更

    • バックトレースの各式を他の表示と区別するために黄色に変更

という所で、Emacs側のコードはこんな感じ
(※フォント指定は環境によって異なり、フォントが探し出せないとエラーになるで注意)

(progn
  ;; topline
  (set-face-font 'sldb-topline-face "01フロップデザイン")
  (set-face-attribute 'sldb-topline-face nil :underline nil)
  (set-face-attribute 'sldb-topline-face nil :inverse-video nil)
  (set-face-attribute 'sldb-topline-face nil :height 145)
  (set-face-attribute 'sldb-topline-face nil :bold t)
  (set-face-foreground 'sldb-topline-face "orange")
  ;; section
  (set-face-font 'sldb-section-face "new century schoolbook")
  (set-face-underline 'sldb-section-face t)
  (set-face-foreground 'sldb-section-face "green")
  ;; conditipn
  (set-face-foreground 'sldb-condition-face "#8888aa")
  ;; etc.
  (set-face-foreground 'sldb-local-name-face "light green")
  (set-face-foreground 'sldb-detailed-frame-line-face "yellow")
  (set-face-foreground 'sldb-frame-label-face "#888888")
  (set-face-foreground 'sldb-frame-line-face "yellow")
  (set-face-foreground 'sldb-restart-number-face "cyan")
  (set-face-foreground 'sldb-restartable-frame-line-face "yellow"))

デフォルト

sldb-normal

カスタマイズ後

sldb-customized

まとめ

 色付けは個々人の趣味があると思いますが、カスタマイズするとかなり見易くなり、バックトレースも眺めてみようという気になりますので、結構おすすめです。


HTML generated by 3bmd in SBCL 1.2.9

Open Generaのメーラー: ZMail

Posted 2015-03-10 11:05:52 GMT

【Open Genera関連タグ: symbolics open-genera genera symbolics-cl まとめて

ZMailとは

 Open Generaは、仮想Lispマシン上のLisp製OSですが、Altoから続くシングルユーザーOSの系譜ということもあり、一通りのユーティリティが付属してきたりします。
標準メーラーは、ZMailですが、これは、CADR時代からあるもので、マニュアルを書いている所からすると実装にはどうもRMSが関係しているようです。

 Open GeneraでのZMailは、mbox形式に対応していて、デフォルトでは、ホストの /usr/spool/mail/user名 にスプールのファイルが置かれれば、メールを取り込むことが可能な様子。
下記は、Symbolics User Group(SLUG)の1986年のメーリングリストのメールをインポートしてみたところです。

zmail

ホストのタイムゾーンをJSTに設定しているのですが、メールの日時がJSTでなくてもJSTに合わせてくれるという芸の細かさ。
また、フォントの指定が使えたようで、メーリングリストでもフォント指定している人がたまにいます。
フォントの設定は、ヘッダに入っていて、

Character-Type-Mappings: (1 0 (NIL 0) (:SWISS :BOLD-ITALIC NIL) "HL12BI")
                         (2 0 (NIL 0) (:SWISS NIL NIL) "HL12")
                         (3 0 (NIL 0) (NIL :BOLD-EXTENDED NIL) "CPTFONTB")
Fonts: CPTFONT, HL12BI, HL12, CPTFONTB

というようなものをZMailが解釈するようです。

メール送信

メールの送信も可能のようですが、自分が試してみたところでは、サーバ側が、ホスト名無しのIPアドレスからの受信を許可するようなものであれば、OKなようです。
色々設定すれば、使えなくもないかもしれません。


HTML generated by 3bmd in SBCL 1.2.9

defclassでリストを生成させる #1

Posted 2015-03-04 09:28:54 GMT

 defstructだと構造体クラスのインスタンスの代わりにリストが生成できたりしますが、

(defstruct (zot/list (:type list) :named)
  x y z)

(make-zot/list) ;=> (ZOT/LIST NIL NIL NIL)

用途も特に考えつきませんが、defclass ではどうやって実現することになるんだろうなと思って試してみました。

(defclass foo/list ()
  ((x :initarg :x :initform nil)
   (y :initarg :y :initform nil)
   (z :initarg :z :initform nil)))

(defmethod allocate-instance ((class (eql (find-class 'foo/list))) &rest initargs) (declare (ignore initargs)) (list :foo/list))

(defmethod shared-initialize ((instance list) slot-names &rest initargs) (declare (ignore slot-names)) (setq instance (nconc instance initargs)))

(defmethod initialize-instance ((instance list) &rest initargs) (apply #'shared-initialize instance t initargs))

こんな感じに定義しました。
これでdefstruct:namedオプションを付けた感じのリストが生成されます。

(make-instance 'foo/list :x 0 :y 1 :z 2)
;=>  (:FOO/LIST :X 0 :Y 1 :Z 2)

:named なリストから change-class できたりすると面白いかもしれませんが、 defstruct のようにリストのアクセサが自動で定義できるわけでもないので、何かに使えそうな、使えなさそうな微妙な所です。

しかしAllegro CLでは定義できない

ちなみに、SBCL、Clozure CL、LispWorks、Allegro CLと試してみましたが、Allegro CLの場合、

(defmethod allocate-instance ((class (eql (find-class 'foo/list))) &rest initargs) ...)

の定義が活きないので、上記の定義では無理のようです。

(eql (find-class 'foo/list)
     (find-class 'foo/list))
;=>  T

ですが、

(allocate-instance (find-class 'foo/list))
;=>  #<FOO/LIST @ #x1005d168a2>

という結果に。何故なのか。

まとめ

その1的に書いてみましたが、何か面白い活用方法が見付かったら続きを書いてみたいと思います。
インスタンスの情報を取り回すのが面倒なので、 :named リストの識別子をクラスのインスタンスにしてみたら面白い気もします(本末転倒気味)


HTML generated by 3bmd in Clozure Common Lisp Version 1.10 (LinuxX8664)

ASDFでパッケージ名とファイル名を同期させる

Posted 2015-03-01 06:59:24 GMT

 以前、ASDFでリードテーブルを指定したりしましたが(#:g1: ASDFでリードテーブルを切り換える)、*readtable* だけでなく、*package* もコンパイル/ロード時にはファイル単位で切り替わります。

 ということで、パッケージでも何かしてみようと思って、ファイル名からパッケージ名を推定させてみたりしました。

コードは非常に簡単で、asdf:performの:aroundで指定したパッケージを束縛します。

(defun get-package (pkg &optional (default (find-package :cl-user)))
  (or (find-package pkg)
      default))

(defmethod file-name-to-package-name ((file asdf:cl-source-file)) (concatenate 'string (string-upcase (asdf:component-name (asdf:component-parent file))) "." (string-upcase (asdf:component-name file))))

(defmethod asdf:perform :around ((operation asdf:compile-op) (file asdf:cl-source-file)) (let ((*package* (get-package (file-name-to-package-name file)))) (call-next-method)))

(defmethod asdf:perform :around ((operation asdf:load-op) (file asdf:cl-source-file)) (let ((*package* (get-package (file-name-to-package-name file)))) (call-next-method)))

 こんな感じに適当に書いてみました。パッケージはとりあえず、先に定義しておくものとします。
また、パッケージ自体自動で作成も可能ですが、そこまで動的でなくても良いだろうということで挑戦しませんでした。
パッケージ探しに失敗した場合は、:cl-userが指定されます。また、ファイルを読み込む時に指定されている変数なので、in-packageで上書きすることが可能です。

 ということで、下記のように書いてasdf:load-systemすると

  • package.lisp

;;;; package.lisp -*- Mode: Lisp;-*- 

(cl:in-package :cl-user)

(defpackage :auto-pkg.auto-pkg (:use :cl) (:export :foo :bar :baz))

(defpackage :auto-pkg.test (:use :cl :fiveam) (:export :foo :bar :baz))

;;; *EOF*

  • auto-pkg.lisp

;;;; auto-pkg.lisp -*- Mode: Lisp;-*- 

(defun foo (x) x)

(defun bar (x) x)

(defun baz (x) x)

;;; *EOF*

ロードが完了すると、こんな感じになります。

(auto-pkg.auto-pkg:foo
 (auto-pkg.auto-pkg:bar
  (auto-pkg.auto-pkg:baz 42)))
;=>  42

まとめ

 試してみて、これは何かに似てるなあと思ったのですが、asdf-package-systemと似た感じの所がありますね。


HTML generated by 3bmd in LispWorks Personal Edition 6.1.1

Lisp machine Lispのカスタマイズ可能なdefstruct

Posted 2015-02-17 18:37:42 GMT

 Common Lispのdefstructは:typeを指定することで、データ構造にリストやベクタを利用可能です。
ふと、:typeでplistやalistが指定できると色々便利なのではないかと思ったのですが、Lisp machine Lispではdefstructで利用するデータ構造をカスタマイズできたことを思い出したので、plistが作れないか試してみました。  マニュアルによると si:defstruct-define-type でカスタマイズ可能な様子。

Open GeneraではLisp machine Lisp(Zetalisp)もCommon Lispと混ぜて利用可能なので、マニュアルを参照しつつ、サンプルコードを適当にいじってみたところ下記のようなコードでdefstructのバックエンドにplistを利用することができました。

定義

(si:defstruct-define-type plist
  (:cons (initialization-list description keyword-options)
   :list `(list ,@(mapcan (lambda (x y)
                            (list (list 'quote (intern (string (car x)) :keyword)) y))
                          (nth 3 description)
                          initialization-list)))
  (:ref (slot-number description argument)
   `(nth (1+ (* 2 ,slot-number)) ,argument)))

動作

(defstruct (bar (:type plist))
  x y z)

(make-bar :x 1 :z 3 :y 2 ) ;=> (:X 1 :Y 2 :Z 3)

(bar-y (make-bar :x 1 :z 3 :y 2 )) ;=> 2

(let ((p (make-bar :x 1 :z 3 :y 2))) (setf (bar-y p) 42) p) ;=> (:X 1 :Y 42 :Z 3)

(apply #'make-bar (make-bar :x 1 :z 3 :y 2 )) ;=> (:X 1 :Y 2 :Z 3)

(defstruct (baz (:type plist)) (x 0) y z)

(make-baz) ;=> (:X 0 :Y NIL :Z NIL)

 plistの形式はキーワード引数を作る時に便利かと思ったのですが、あれば便利なようなそうでもないような。

まとめ

 MIT Lispの魔窟にLOOP、FORMATが良く知られていますが、他に、DEFSTRUCT、Lambda-Listもあります。
DEFSTRUCTの辺りはまだ良く調べていませんが、面白そうなので、この辺りも探ってみたいところです。


HTML generated by 3bmd in SBCL 1.2.8

お馴染の-*- Var: Val; -*- の呼び名

Posted 2015-02-16 09:48:26 GMT

 GNU Emacs等のエディタで良く使われていてお馴染のファイルの先頭の -*- -*-ですが、一体何という名前なのでしょう。
かなり昔からLispのファイルには大抵付いていてLispプログラマには馴染み深いものですが、判然としないまま今迄過して来ました。
ここで一つ、果して正式名称があるのか調べてみました。

Emacsでは

まずEmacsのマニュアルを眺めてみましたが、‘-*-’ line みたいに書かれているので何と呼んだら良いのか書いてる人も判然としていないのではないでしょうか。

ローカル変数の指定の仕方も-*-行に書く方法とファイル後部にまとめて書く方法があるのでfile variableなんとかという呼び方もされないのかもしれません。
敢えて挙げるなら add-file-local-variable-prop-line というコマンドがあるので、File local variable property line でしょうか。

MIT ATラボでは

 GNU Emacsより古くは、MITのAIラボのファイルでの利用ですが、調べてみたところ、どうも File property list と呼んでいるようです。
出現時期は、1978の7月後半らしいのですが、これより前は皆無なのに、この後に爆発的に増えるようなので、何かこのタイミングでブレイクする切っ掛けがあったのでしょう。
1978年7月以降、Lispファイルに限らずMIDASからテキストファイルまで付けられまくるようになります。
Lispマシングループ発祥なのか、MACLISPグループが発祥なのかは良く分かっていません。

Symbolicsでは

 Symbolicsでは、File propertyというとファイル自身の属性と混同して紛らわしい為なのか、File attribule list と呼んでるようです。

File property list と Lisp

 ちなみに、Emacsでは単にモードを指定するだけの物ですが、Lisp machine Lispでは、シンタックスや、基数、リードテーブル、パッケージを指定するものとして機能していました。
この辺りは、Common Lispでは廃止になり、(in-package :foo) などとしますが、Lisp machine Lispでは、

;;; -*- Package: foo; -*- 

という風に書く感じです。
最近だと、named-readtables等と使って (in-readtable :foo) 等とすることもありますが、これは

;;; -*- Readtable: foo; -*- 

という感じ。 構文も切り換えることが可能で、

;;; -*- Syntax: Zetalisp; -*- 

;;; -*- Syntax: Common-lisp; -*- 

を始め(ZetalispとCommon Lispではエスケープ文字が/と\で違っている)、中置記法のCGOLの指定などというものもあったようです。

;;; -*- Syntax: CGOL; -*- 

まとめ

とりあえず自分は、由緒正しく File property list と呼んでみようかなというところですが、まあ多分そう呼んでも通じなさそうです。


HTML generated by 3bmd in SBCL 1.2.8

Common Lispのexportの問題あれこれ

Posted 2015-02-14 08:33:24 GMT

トップレベルでexportを使う

トップレベルでexportを使うコードはCLtL2以降(1990年位)のコードではあまり見掛けなくなりましたが、最近cl-annotや、hu.dwim系ユーティリティの影響なのか増えてきたように思います。
しかし、exportは関数なので、そのままトップレベルに置くと、ロードまで評価されない問題があります。

 CLtL1の時代には、何故多かったのかと言うと、パッケージ関係の関数は、コンパイル時にも評価されるという特別な扱いを受けていたからですが、CLtL2に記載があるようにこの挙動はANSI CLでは削除されました。

ANSI CLでの動作例を挙げると、下記のようなfoo.lispというファイルがあったとすれば、

(cl:defpackage :foo
  (:use))

(cl:in-package :foo)

(cl:export 'foo)

コンパイルしてロードすると

(compile-file "foo.lisp")

(find-symbol "FOO" :foo) ;=> FOO::FOO ; :INTERNAL

(load "foo.lisp") ;=> T

(find-symbol "FOO" :foo) ;=> FOO:FOO ; :EXTERNAL

こんな具合です。

 これを問題とするかどうかですが、書き手の頭の中では、恐らくリード時に限り無く近いタイミングでexportされることを想定しているであろうことと、評価タイミングによる余計な問題の可能性を持ち込むので、やめておいた方が良いのではないかと思います。
また、コンパイル時にも評価されるものとして導入されたものなのに、されなくなった状態で利用するというのも、本来の意図とは違った利用形態になってしまった感があります。
といっても、この問題は、コンパイル時にも評価されるように、eval-whenで囲めば解決ではあります。

(eval-when (:compile-toplevel :load-toplevel :execute)
  (export 'foo))

exportを散らかす

 トップレベルのexport問題とは少し違った問題に、defpackageでexportするシンボルを纏めないで、ファイルに分散させるというのがあります。 これはSBCLでエラーになるのでウザいところですが、一応SBCLの言い分としては、ANSI CLのdefpackageの

If the new definition is at variance with the current state of that package, the consequences are undefined

という内容を重視しているためのようです。

 defpackageで宣言されたものをいじったら結果は未定義ということですが、個別のパッケージ系の関数や、再度defpackageで上書き宣言してもパッケージの内容がどうなるかは未定義です。
恐らくdefpackageはいかにも宣言という感じで静的に使うのかもしれません。

では、動的に追加したい場合はどうしたら良いのかとなりますが、SBCLの開発者である、Christophe Rhodes氏が質問に答えている内容からすると、make-packageを使えば良いということです。

つまり完全に動的に扱えば良いじゃないということですね。
しかし、exportを分散させて書いている書き手は、面倒臭いから書いてあるだけで、動的にしようという意識は恐らくないのではないかと思います。

何故こういう事態になったか

 CLtL1時代では、パッケージ系関数である make-package、 in-package、 shadow、 shadowing-import、 export、 unexport、 use-package、 unuse-package はコンパイル時にも評価されるという特別な扱いでした。

 しかし特別扱いするのは色々厄介であるということで、特別扱いするのは無しになり、proclaimの他にdeclaimが追加されたり、in-packageが関数からマクロになり、個別のパッケージ関数のコンパイル時の機能を代替するものとして、マクロのdefpackageが導入されました。

 この流れからすると、defpackageで宣言できる内容は、個別のパッケージ関数を使ってくれるな、という風にも読めますが、個別のパッケージ関数は廃止予定にも廃止にもなりませんでした。
ということで、原点回帰してしまったような使い方をする人が出てきてしまうことになったのだと思います。

どうすれば良いのか

 defpackageしか使わないことで簡単に解決できますが、定義の近くにexportがあるのが便利、というのも分かります。
そうなると、ツールかなにかで実現することになるかと思いますが、slime だと、

(require 'slime-package-fu)

すれば、 slime-export-symbol-at-point というコマンドが使えます。
export したいシンボルの上で実行すると、defpackageを見付けて:export節に挿入してくれるというもの。

 このコマンドもなんか面倒臭いという場合は、個別でexportしている所を、exportの疑似宣言にしてしまい開発時にはexport的な挙動をし、デプロイする時には、まとめてdefpackageを生成するような仕組みでもあれば楽なのではないでしょうか。
これも別に作るのは難しくないと思います。

 また、export を個別に使うことを通したければ、defpackageを利用しないで、make-packageすることになるのかなと思います。

まとめ

 個人的なスタイル上の好き嫌いで書き始めましたが、色々調べてみたら割合にちゃんとした流れと理由がありました。
個人的には、CLtL1時代コードを触る機会が結構ありますが、評価フェーズがはっきりしないコードに悩まされることが多いので、exportがちらばったコードを目にすると嫌な予感がしてしまいます。
Allegro CL等ではすんなりビルドできたりするのですが、この辺りのケア(comp:*cltl1-compile-file-toplevel-compatibility-p*等々)があるのが大きいのかもしれませんね。


HTML generated by 3bmd in SBCL 1.2.8

Older entries (1911 remaining)