#:g1: frontpage

 

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

Tachyon CLのオブジェクトトレーサをまねる

Posted 2015-02-12 17:44:59 GMT

 伝説のCより速い処理系(1993年当時) Tachyon Common Lisp (C=光 より速いのでタキオンらしい)には、オブジェクトトレーサというものがあったらしいのですが、どうにか実現できないか試してみました。

まず、このオブジェクトトレーサとはどんなものかですが、関数のトレースと同じようにオブジェクトを追跡するものです。
上述の文献でのオブジェクトトレーサの仕様としては、

  • トレースできるのは、総称関数が触った時だけ
  • 特定のクラスのオブジェクトだけ(トレースのオン/オフは制御可能)

とのこと。

 しばし考えてみましたが、総称関数が定義された時に、オブジェクトを表示するメソッドを追加してしまえば実現できるのではないか、ということで書いてみました。

(defvar *original-initialize-instance/after*
  (c2mop:method-function
   (find-method #'initialize-instance
                '(:after)
                (list (find-class 'standard-generic-function)))))

(defvar *traced-instances* (make-hash-table))

(defun display-object (gf instance mode out) (loop (format out "CURRENT GENERIC-FUNCTION~25T: ~A~%" gf) (format out "INSTANCE ~25T: ~A~%" instance) (dolist (s (c2mop:class-slots (class-of instance))) (let ((slot-name (c2mop:slot-definition-name s))) (format out "~3TSLOT-NAME ~25T: ~S~%" slot-name) (format out "~3TSLOT-VALUE ~25T: ~A~2%" (if (slot-boundp instance slot-name) (slot-value instance slot-name) "UNBOUND")))) (format out "TRACE MODE ~25T: ~A-TRACED~%" mode) (when (eq :non-interactive mode) (return instance)) ;; (format *query-io* "Put S-form or Hit c(ontinue) ,~%then Hit return>") (let ((ans (read *query-io*))) (if (and (symbolp ans) (string= :c ans)) (return instance) (print (eval ans))))))

(defun trace-object (obj &key (trace-mode :non-interactive) ) (setf (gethash obj *traced-instances*) trace-mode) obj)

(defun untrace-object (&rest specs) (if (null specs) (clrhash *traced-instances*) (dolist (s specs) (remhash s *traced-instances*))))

#+sbcl (defun install-tracer () (defmethod initialize-instance :after ((self standard-generic-function) &rest args &key (lambda-list nil lambda-list-p) argument-precedence-order) (declare (ignore lambda-list-p argument-precedence-order)) (funcall *original-initialize-instance/after* (cons self args) nil) (add-method self (make-instance 'standard-method :function (lambda (args next) (declare (ignore next)) (dolist (x args) (let ((mode (gethash x *traced-instances*))) (when (and #|(eq (type-of (class-of x)) 'tracable-mixin)|# mode) (display-object (c2mop:generic-function-name self) x mode *standard-output*))))) :lambda-list lambda-list :qualifiers '(:before) :specializers (mapcar (constantly (find-class T)) (c2mop:extract-specializer-names lambda-list))))))

#+sbcl (defun uninstall-tracer () (setf (slot-value (find-method #'initialize-instance '(:after) (list (find-class 'standard-generic-function))) 'sb-pcl::%function) *original-initialize-instance/after*))

 総称関数の引数を generic-function-lambda-list で取得して、総称関数にmethod :berore(t t* …)なオブジェクト表示用のメソッドをを追加します。
クラスでの範囲制御は、専用のメタクラスを定義して、それを判定すれば可能かと思いますが、それだとクラスの定義でメタクラスを指定することになって、面倒だなあということで、ここはスルーしました。
思い切ってstandard-classにtracedフラグのスロットを追加してしまっても良いかもしれませんが…。

これで、こんな感じに動作します。

(defgeneric badialma (x y z &optional a))

(defmethod badialma (x y z &optional a) (list x y z a))

(print obj) ;>> ;>> #<FOO {10054A5CA3}> ;=> #<FOO {10054A5CA3}>

(badialma 1 2 3 4) ;=> (1 2 3 4)

(badialma 1 2 3 obj) ;>> CURRENT GENERIC-FUNCTION : BADIALMA ;>> INSTANCE : #<FOO {1005AED063}> ;>> SLOT-NAME : A ;>> SLOT-VALUE : UNBOUND ;>> ;>> SLOT-NAME : B ;>> SLOT-VALUE : UNBOUND ;>> ;>> SLOT-NAME : C ;>> SLOT-VALUE : UNBOUND ;>> ;>> TRACE MODE : NON-INTERACTIVE-TRACED ;>> ;=> (1 2 3 #<FOO {1005AED063}>)

(badialma 1 2 obj 3) ;>> CURRENT GENERIC-FUNCTION : BADIALMA ;>> INSTANCE : #<FOO {1005AED063}> ;>> SLOT-NAME : A ;>> SLOT-VALUE : UNBOUND ;>> ;>> SLOT-NAME : B ;>> SLOT-VALUE : UNBOUND ;>> ;>> SLOT-NAME : C ;>> SLOT-VALUE : UNBOUND ;>> ;>> TRACE MODE : NON-INTERACTIVE-TRACED ;>> ;=> (1 2 #<FOO {1005AED063}> 3)

 まあまあ悪くもなさそうですが、やはり上記の安直な実装には問題があります。

  1. defgeneric を定義しないで、defmethodする場合、(initialize-instance :after standard-generic-function) で generic-function-lambda-list で取れる引数情報が NIL になるためうまくない(SBCLの場合)
  2. 表示用メソッドを追加してしまうので既存のメソッドがあった場合どうするか
  3. メソッドを追加する方法なので、既存の総称関数には、メソッドを新規に追加する必要がある

等々

まとめ

 やはり、後付けでは色々厳しい感じですが、ちょっと試してみた感じでは、案外トレースしたいオブジェクトを掴む方法がないものだなと思いました。
掴む方法がないというのは、オブジェクトが大域の変数に束縛されていたりすることは案外少ないようなので、別途トレースの為にどうにかして参照する方法を考えなくてはいけないということです。 make-instance のafterメソッドかなにか掴んでしまえば良いのかもしれません。

 という所でしたが、オブジェクトのトレースについて何か面白いツール、面白い方法がありましたら是非教えてください。


HTML generated by 3bmd in SBCL 1.2.8

クロージャの比較とnamed-lambda

Posted 2015-02-10 07:35:55 GMT

 SBCL等には、named-lambdaというものがあります。 Lisp処理系においては、named-lambdaというものには、過去には案外バリエーションがあるようですが、LISP 1.5でいうlabelと等価なものだったりだったりすることが多いようです。

 SBCLのものは、再帰の為の構文でもなく、blockを作る訳でもなく存在が謎でしたが、クロージャの比較、あるいは「同じ」とはどういうことかを読んでいて、

(lambda-with-tag tag (x) (+ x a))

のようなものを目にし、なるほど比較の為のものなのかもしれない、と思い、早速調べてみました。

結論からいうと、SBCLの場合、残念ながら

(defun foo (a flag1 flag2)
  (let ((p (lambda (x) (+ x a))))
    (bar (if flag1 #'identity p)
         (if flag2 #'identity p))))
;==>
(defun foo/ (a flag1 flag2)
  (bar (if flag1 #'identity (lambda (x) (+ x a)))
       (if flag2 #'identity (lambda (x) (+ x a))))) ;上とeqなクロージャ

のような賢い最適化をしてくれることもないようで、

named-lambdaも

(sb-int:named-lambda tag (x) (+ x a))
;=>  #<FUNCTION TAG {100A7A05CB}>

(sb-int:named-lambda tag (x) (+ x a))
;=>  #<FUNCTION TAG {100A7DFA4B}>

このように関数に同じ名前は付いても同じものにはなりません。
しかし、それでは、何に使うのかというと、ソースコードの場所の記録だったりデバッグ情報として記録したりしているようです。
ちなみに、%fun-name というものを使えば、named-lambdaを使わなくても名前は付けられるようです。

(let ((f (lambda (x) x)))
  (setf (sb-kernel:%fun-name f) "foo")
  f)
;=>  #<FUNCTION "foo" {100AF7738B}>

これだと、named-lambdaは不要な気もしますが、まあ、名前を付けるための構文があったら便利ということなのでしょうか。

いずれにせよ、SBCLでは、クロージャーに名前を付けて、クロージャを判別することは可能ではあります。


HTML generated by 3bmd in International Allegro CL Enterprise Edition 8.2 [64-bit Linux (x86-64)] (Jan 15, 2015 12:49)

Quickmonkeys という手抜きのモンキーパッチのツールを作りました

Posted 2015-02-08 01:29:09 GMT

パッチのツールが欲しい…

 Quicklispのお蔭でライブラリを導入するのは非常に簡単になりました。
ライブラリの導入は簡単になったのですが、割合に動かない場合も多かったりして、その辺りは適当にソースを直に直してしまうのですが、うっかりしているとQuicklispが更新された時に上書きされて消えてしまいます。
パッチ用のプロジェクトを作成してロードしても良いのですが、これも面倒だということで、修正したファイルを元のシステムと差し替えてロードするツールを作ってみました。

特に凝ったことはしておらず、安直にASDFのsystemのファイルコンポーネントを差し替えます。

パッチの手順としては、

  1. 修正したいファイルを quickmonkeys/monkeys/システム名/ 以下にコピー
  2. monkeys.lisp に defpatch でパッチを定義
  3. (ql:quickload システム) かなにかでロード

です。
必要になったらロードするようにしてみましたが、適当に作ったため初回のロードで転けることがあるようです。 そういう場合は、

(asdf:load-syste システム :force '(システム))

しましょう。

まとめ

MIT Lispマシンにはパッチの仕組みがあり非常に便利そうなのですが、Common Lispにも定番ツールが欲しいところですね。


HTML generated by 3bmd in International Allegro CL Enterprise Edition 8.2 [64-bit Linux (x86-64)] (Jan 15, 2015 12:49)

CL Test Grid活用のすすめ

Posted 2015-02-06 00:14:54 GMT

CL Test Grid とは

CL Test Gridはライブラリの動作/ビルド状況をチェックしてウェブで閲覧できる仕組みです。
現在のところQuicklisp収録のもののチェックが主なところ。

Quicklispで導入したライブラリがビルドできない…

Twitter等でCommon Lisp関係のつぶやきを眺めていると、ライブラリが動く動かないの発言が割合にあります。
まあ、動かないライブラリが悪いのですが、折角こういう仕組みがあるので試したいライブラリがどういう状況なのかをCL Test Gridでチェックするようにしてみてはどうでしょうか。

quicksearch のようなツールで確認できたりすると嬉しいかもしれないですね。


HTML generated by 3bmd in SBCL 1.2.8

コードウォーキングとCLtL2の環境系関数

Posted 2015-01-22 06:58:25 GMT

 ローカルな変数や関数のスコープを持つ構文を自作したくて色々試してみたのですが、結局のところコードウォークが必要になるというところまでは分かりました。
実現したいことは、こんな感じのことです。

 こういう構文を実現したいのですが、

(let ((x 0))
  (mylet ((x 1))
    ...
    ;; ここのxはgensymされたシンボルに置き換えられる
    (let ((x 2))
      ...
      ;; myletの内側には影響はない
      )))

(flet ((foo (x) ...)) (fsubstlet ((foo list)) ... ;; ここでは、fooと書けばlistに置き換えられる (flet ((foo (x) ...)) ... ;; fsubstletの内側には影響はない )))

わかりづらいので、hu.dwim.walker を使った実装を書いてしまいますが、こんな感じになります。

;;; mylet
(Defmacro mylet (var val &Body body)
  (Let* ((gname (gensym))
         (let-form (walk-form
                    `(Let ((,var ,val))
                       ,@body)))
         (bind (First (bindings-of let-form))))
    (Setf (name-of bind) gname)
    (map-ast (Lambda (x)
               (If (And (typep x 'lexical-variable-reference-form)
                        (Eq bind (definition-of x)))
                   (Setf (name-of x) gname)
                   x))
             (body-of let-form))
    (unwalk-form let-form)))

(Let ((x 420)) (mylet x x (List x (Let ((x 42)) x) (Funcall (Lambda () x))))) ;===> (LET ((X 420)) (LET ((#:G2154 X)) (LIST #:G2154 (LET ((X 42)) X) (FUNCALL #'(LAMBDA () #:G2154))))) ;=> (420 42 420)

;;; fsubstlet (Defmacro fsubstlet ((&rest name-orig) &body body) (Let* ((form (walk-form `(Flet (,@(Mapcar (Lambda (x) `(,(car x) ())) name-orig)) ,@body))) (binds (bindings-of form))) (map-ast (Lambda (x) (Cond ((And (Typep x 'lexical-function-object-form) (member (definition-of x) binds) (Assoc (name-of x) name-orig)) (Setf (name-of x) (second (Assoc (name-of x) name-orig )))) ;; ((And (Typep x 'lexical-application-form) (member (definition-of x) binds) (Assoc (operator-of x) name-orig)) (Setf (operator-of x) (second (Assoc (operator-of x) name-orig ))))) x) form) (unwalk-form form)))

(Flet ((myfunc (x) x)) (List (fsubstlet ((myfunc list)) (List (myfunc 42) #'myfunc (Flet ((myfunc (x) x)) (myfunc 42)))))) ;=> (((42) #<FUNCTION LIST> 42)) ;==> (FLET ((MYFUNC (X) X)) (LIST (FLET ((MYFUNC () ;本来は不要 )) (LIST (LIST 42) #'LIST (FLET ((MYFUNC (X) X)) (MYFUNC 42)))))) ;=> (((42) #<FUNCTION LIST> 42))

簡単に言ってしまえば、ローカル環境にサンドイッチされた部分でも変数/関数を正しく置き換えたいということです。

CLtL2の環境を扱う関数で実装は可能か

 上記の構文をCLtL2の環境を扱う関数で実現しようと思ったのですが、variable-informationにしろ function-information にしろ、名前がレキシカルかどうかの情報は取得できるものの、どの束縛に属しているかは分からないようです。
置き換えに際しては、どの束縛に属しているかが分からなければ、正確に置き換えることは不可能です。

 ローカルマクロは、名前と展開関数をセットで扱うことが可能なので、augment-environmentで環境を拡張してやれば、macrolet も自作可能ですが、変数と関数はaugment-environmentでは、名前しか拡張できないので中途半端な道具に思えます。
果して、CLtL2の環境を扱う関数で実装は可能なのでしょうか。


HTML generated by 3bmd in SBCL 1.2.7

今日のバグ: walk-form / SBCL

Posted 2015-01-20 10:42:55 GMT

ライブラリとプラットフォーム

  • walk-form SBCLの組み込みコードウォーカー
  • SBCL

問題となる現象

 見慣れないスペシャルフォームに遭遇するとエラーが起きる

(sb-walker:walk-form '(SB-C::%FUNCALL))
;!> unexpected special form SB-C::%FUNCALL
;!> This is probably a bug in SBCL itself. (Alternatively, SBCL
;!> might have been corrupted by bad user code, e.g. by an undefined
;!> Lisp operation like (FMAKUNBOUND 'COMPILE), or by stray pointers
;!> from alien code or from unsafe Lisp code; or there might be a
;!> bug in the OS or hardware that SBCL is running on.) If it seems
;!> to be a bug in SBCL itself, the maintainers would like to know
;!> about it. Bug reports are welcome on the SBCL mailing lists,
;!> which you can find at <http://sbcl.sourceforge.net/>.

デバッグ手順

 特定のオペレーターが含まれたフォームでしか起きないので、そのオペレーターが関数なのかマクロなのか確認したところ、スペシャルオペレーターだった。
 スペシャルオペレーターということは、コードウォーカーはテンプレートの情報がなければ、どういう風に展開して良いか分からない筈。
ということで、テンプレートを書いてやるとエラーは出なくなる。

(sb-walker:define-walker-template sb-c::%funcall (nil sb-walker::repeat (eval)))
(sb-walker:define-walker-template sb-sys:%primitive (nil eval sb-walker::repeat (eval)))
(sb-walker:define-walker-template sb-c::global-function (nil quote))
(sb-walker:define-walker-template sb-c::%within-cleanup (nil eval eval sb-walker::repeat (eval)))
(sb-walker:define-walker-template sb-c::%escape-fun (nil quote))
(sb-walker:define-walker-template sb-c::%cleanup-fun (nil quote))
(sb-walker:define-walker-template sb-c::%%allocate-closures (nil sb-walker::repeat (quote)))

等々。

バグ判定

 sb-walker:walk-form が未知のスペシャルオペレーターに遭遇した場合の対策をしていないのが原因。
しかし、原因となるスペシャルオペレーターは、主にIR1の中だけで使われたりするもののようなので、通常のマクロ展開で遭遇するものとも思えず微妙。
とはいえ、一応、コードウォーカーは、処理系内の全てのスペシャルオペレーターを把握しておくべきな気もする、というところ。

報告状況

 なんとも微妙なので報告するかどうかも微妙。 SBCLの場合、報告だけは気軽にできるので、適当に報告だけしてみるのもありかなとは思う。

まとめ

 どうでも良いんだけども見付けてしまったので気になる。


HTML generated by 3bmd in SBCL 1.2.7

Older entries (1905 remaining)