#:g1: frontpage

 

コードウォーキングと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

オープンソースになったCorman Lispをビルドしてみた

Posted 2015-01-18 15:39:15 GMT

 先日オープンソースとなったCorman Lispですが、VisualStudio 2005でビルド可能らしいのでビルドしてみました。

ビルドするためのファイル入手と手順

とりあえず手順ですが、

  1. VisualStudio 2005体験版を入手

  2. GitHubから、Corman Lispのソースをチェックアウト

  3. VS2005から、srcファイルを開き、プロジェクトをビルド

であっさりビルドできます。

ビルドした Corman Lisp を起動

 さて、Corman Lisp の起動ですが、どうもLispイメージが別途必要のようです。
ざっとイメージの生成方法についてソース内を探してみましたが、見付けられず。
しょうがないので試しに、Corman Lispの体験版をインストールした時にインストールされてあった CormanLisp.img をディレクトリに置いてみましたが、これで起動できました。

 さて、ビルドして起動したものは、どうやら体験版になります。
オープンソースになったものの、ライセンスチェックの回避の改造をするのはなんだか微妙な気がしますが、 license/license.cpp の CheckRegistration に細工してビルドしたところ、体験版の制限はなくなるようです。

 起動は、IDE付きの、CormanLisp.exeと、コンソール版の、clconsole.exeを選べるようです。 尚、clconsole.exe は、Linux上のwineでも実行できました。

ASDF や Quicklisp への対応具合を確認

 ASDF や Quicklisp への対応具合はどんなものだったかなと思い、Quicklispのインストールを試してみましたが、そのままでは動きません。 これらの対応については、割合にしんどそうに感じました。
 ASDFにもVer. 1あたりでCorman Lispは置いてかれた感じみたいなので、ASDFを動かすにもASDFの方を修正しないといけません。
主に問題になるところとしては、

  • built-in-classがない
  • defpackageの定義に凡ミスがある

位ですが、

(defclass built-in-class (standard-class) ())

したり、Sys/defpackage.lisp の build-import-forms を修正したりします。

,into-pkg-name => ',into-pkg-name

ライブラリの Corman Lisp への対応状況

Quicklisp収録のライブラリをgrepして確認してみましたが、

  • asdf-driver
  • asdf-install
  • asdf-utils
  • cells-gtk3
  • cl-fad
  • cl-html-parse
  • cl-launch
  • cl-ppcre
  • clfswm
  • clpython
  • com.informatimago
  • conium
  • fomus
  • gbbopen
  • html-template
  • hyperobject
  • kmrcl
  • lift
  • lisa
  • lml
  • mcclim
  • mel-base
  • metatilities-base
  • pg
  • plain-odbc
  • portableaserve
  • readable
  • slime
  • snmp
  • stumpwm
  • tbnl
  • temporary-file
  • trivial-shell
  • uffi
  • uiop
  • umlisp-orf
  • url-rewrite
  • usocket
  • xcvb

あたりは、Corman Lispに対応してそうな気配はあります。
とりあえず kmrcl はロードできました。

まとめ

 定番の開発セットであるSLIME+Quicklispで使うには若干厳しい感じですが、こつこつ対応していけばそのうち動かせるかもしれません。 対応する場合ですが、Corman Lispは、案外ANSI CL準拠度が高くないようなので、この辺がネックになる気はしています。    


HTML generated by 3bmd in CLISP 2.49+ (2010-07-17) (built 3630442924) (memory 3630443166)

今日のバグ: 3bmd / CLISP

Posted 2015-01-17 15:00:00 GMT

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

  • 3bmd
  • CLISP

の組み合わせ

問題となる現象

 CLISPでビルドできない

<: NIL is not a real number

と怒られる。

デバッグ手順

 エラーの内容からして、どこかで(< nil …)みたいなことになってるんだろうなと推測。
コンパイルのログを眺めて、extensions.lispの中で起きていることを確認。
どの関数かまでは分からないので、一個ずつ評価して確認したところ、ADD-EXPRESSION-TO-LISTの中で発生していることが判明。
この中で<を使っているところといえば、

(< max min)

位なので、ここで発生かということで、

(< (or max 0) (or min 0))

みたいにしたところビルドできた。

バグ判定

 結論から書くと、3bmd側のバグ。
ただLOOPの仕様の重箱の隅的なところで、この仕様を把握している人は少ないと思う。
どのようなものかというと、最短の再現コードでは、


(Loop :repeat 0 :maximize a)
;=>  NIL

となる。
CLISPでは、NILが返っているが、SBCL等0が返る。この0が返る動作に3bmdのコードは依存していた。
 調べてみると、これは、仕様上処理系未定義の振舞い。

If the maximize or minimize clause is never executed, the accumulated value is unspecified.

上記位なら、なんとなく変なコードだなと思うけれど、今回のコードのように


(Loop :for i :in list
      :When (eql :foo i)
        :Return 8
      :else :maximize i)

となると、maximizeが実行されない可能性があるかは、一見して分からないと思う。
この場合、listが '() の場合、未定義。

 よってポータブルに書くとすれば、最低一回は回すようにデフォルト値をリストにコンスして、


(Loop :for i :in (cons 0 list)
      :When (eql :foo i)
        :Return 8
      :else :maximize i)

こんな感じになるだろうか。
into節で累積させて、finallyで返すというのも考えたが、intoで貯める変数の初期値はどうなのかと考えると同じことに思えたので、0回のことを考えるより最低1回は回すのが良いんじゃないかということに。

報告状況

 Quicklisp版のソースはGitHub上にある。
プルリク作ろうかなと思っているが、気が乗らず。報告だけしてみようか、というところ。

まとめ

 すっかり忘れていたが、ちょっと前にLOOPの実装がばらばらなので使い物にならない、というようなことを言っている方がいて、その方が問題としていたコードだった。
そんなことあるのかなあと思って以前調べたコードだったが、仕様としては未定義なので書き手の問題ですね。
一回も回らない場合最大(小)値が0であることが自然かどうかは意見が分かれそうなので未定義というもの分からなくはないが、ちょっと厄介。

 繰り返しで、繰り返しが回らない場合のことを考えるのは割合に難しそう。


HTML generated by 3bmd in CLISP 2.49+ (2010-07-17) (built 3630442924) (memory 3630443166)

今日のバグ: chirp / CLISP

Posted 2015-01-16 20:18:08 GMT

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

  • chirp
  • CLISP

の組み合わせ

問題となる現象

 CLISPでコンパイルできない

FIND-CLASS: FIXNUM does not name a class

と怒られる。

デバッグ手順

 対象のソースを眺めて、メソッドの引数でfixnumを指定している所をintegerに直す。  

バグ判定

 原因は、

(Defmethod foo ((x fixnum)) x)

のようなものをCLISPが許可していないことにある。 fixnumは、クラスではなくTypeなので、この場合CLISPは正しい。

ちなみにintegerはSystem Class。
メソッドがfixnumを受け付けるのは処理系の拡張なんでしょうか。

報告状況

 Quicklisp版のソースはGitHub上にある。
プルリク作ろうかなと思っているが、気が乗らず。報告だけしてみようか、というところ。

 そもそもCLISPだと、Quicklisp版のdrakmaが上手く動かない。
元を辿れば、usocketらしいが、http-requestのconnection-timeoutをnilにしてやらないといけない。
というわけで、chirpだけ直してもQuicklisp環境ではまともに動かない、というところで報告のモチベーションが上がらない。

まとめ

 暇潰しに、色々なプラットフォームでビルドしてみるのもバグ出しには効果的。 仕様の理解も深まる。


HTML generated by 3bmd in CLISP 2.49 (2010-07-07) (built on setq [127.0.1.1])

今日のバグ: cl-oauth / Allegro CL

Posted 2015-01-15 20:57:11 GMT

 遭遇したバグをメモしてみるのも面白いかなと思ったのでメモってみることにしました。 本日は、 cl-oauth のバグ?に遭遇。

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

  • cl-oauth
  • Allegro CL

の組み合わせ

問題となる現象

 twitter のクライアントで cl-oauth を使っていてAllegro CLでだけ認証が失敗する。
なんでかなと思って調べてみた。

デバッグ方法

 とりあえず OAUTH:ACCESS-PROTECTED-RESOURCE に TRACE を実行し、送信した結果を確認。
SLIMEで、SBCLも起動して同じく trace して同時実行。
SBCLが、 oauth_signature=....%3D

と送信している箇所で、Allegro CLが、

oauth_signature=....%3d

だったので、%エンコーディングの大文字小文字の違いが怪しいと思い、Allegro CL の出力を大文字にしたものを送信したところ通る。
これが原因っぽいので、下請け関数を辿っていくと、大元は、CL-OAUTH:URL-ENCODE だった。
これが

(format s "%~2,'0x" octet)

のようなことをしていて、Allegro CLが、

(Format T "~X" 255)
;-> ff
;=> NIL

なので、

(format s "%~:@(~2,'0x~)" octet)

とでもしないといけない。

バグ判定

 まず、oauth 1.0での十六進数のエンコーディングは大文字でなくてはいけないとのことなので、小文字では駄目なのは確実。

  • http://oauth.net/core/1.0/#encoding_parameters

Hexadecimal characters in encodings MUST be upper case.

 それで大文字にならない原因となる Allegro CL のこの挙動は、 ANSI Common Lisp 的に合法なのかは HyperSpec を眺めても良く分からなかった。
 この挙動が非合法なら、 cl-oauth のバグではなく、Allegro CLみたいな挙動をする処理系があるから困っちゃいますよねという話になる。

 ちなみに、Allegro CLは、こんな感じに、*print-case* からも独立した挙動の様子(独立なのは他の処理系も一緒)

(Write 255 :base 16. :case :upcase :escape nil)
;>>  ff
;=>  255

報告状況

Quicklisp版のソースはGitHub上にある。
プルリク作ろうかなと思っているが、気が乗らず。報告だけしてみようか、というところ。

まとめ

ちゃんと書くと結構長くなる。


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

SLIMEでマクロの展開結果を実行したい

Posted 2015-01-12 13:53:51 GMT

 展開されたマクロを実行したいことなんてあるのか、というところですが、自分は実際結構あります。
大抵デバッグ時の話で、何がなにやら分からないような場合が多いですが、とりあえず分割して問題の原因を突き止めるために展開結果を実行したりコンパイルしたりするのが有効なこともあります。
 この際に問題になるのが、gensymされたシンボルが未束縛になってしまうことです。

(Loop :for i :from 0 :repeat 42 :collect i)
;==> 
(BLOCK NIL
  (LET ((I 0))
    (DECLARE (TYPE NUMBER I))
    (LET ((#:LOOP-REPEAT-1535 (CEILING 42)))
      (DECLARE (TYPE (MOD 43) #:LOOP-REPEAT-1535))
      (LET* ((#:LOOP-LIST-HEAD-1536 (LIST NIL))
             (#:LOOP-LIST-TAIL-1537 #:LOOP-LIST-HEAD-1536))
        (TAGBODY
         SB-LOOP::NEXT-LOOP
          (IF (<= #:LOOP-REPEAT-1535 0)
              (GO SB-LOOP::END-LOOP)
              (LET* ((#:G1539 1) (#:NEW1538 (- #:LOOP-REPEAT-1535 #:G1539)))
                (SETQ #:LOOP-REPEAT-1535 #:NEW1538)))
          (RPLACD #:LOOP-LIST-TAIL-1537 (SETQ #:LOOP-LIST-TAIL-1537 (LIST I)))
          (SETQ I (1+ I))
          (GO SB-LOOP::NEXT-LOOP)
         SB-LOOP::END-LOOP
          (RETURN-FROM NIL (CDR #:LOOP-LIST-HEAD-1536)))))))
;!> The variable #:LOOP-LIST-HEAD-1536 is unbound.

こんな感じですが、gensymされたシンボルを置き換えたいところ。

 SLIMEでは、 MAXROEXPAND した印字結果を表示しているのですが、印字しているということは、*PRINT-GENSYM* をNILに設定すれば#:は印字されません。
ということで、 *PRINT-GENSYM* をNILにするとこんな感じの結果になり、実行することも可能です。

(BLOCK NIL
  (LET ((I 0))
    (DECLARE (TYPE NUMBER I))
    (LET ((LOOP-REPEAT-1540 (CEILING 42)))
      (DECLARE (TYPE (MOD 43) LOOP-REPEAT-1540))
      (LET* ((LOOP-LIST-HEAD-1541 (LIST NIL))
             (LOOP-LIST-TAIL-1542 LOOP-LIST-HEAD-1541))
        (TAGBODY
         SB-LOOP::NEXT-LOOP
          (IF (<= LOOP-REPEAT-1540 0)
              (GO SB-LOOP::END-LOOP)
              (LET* ((G1544 1) (NEW1543 (- LOOP-REPEAT-1540 G1544)))
                (SETQ LOOP-REPEAT-1540 NEW1543)))
          (RPLACD LOOP-LIST-TAIL-1542 (SETQ LOOP-LIST-TAIL-1542 (LIST I)))
          (SETQ I (1+ I))
          (GO SB-LOOP::NEXT-LOOP)
         SB-LOOP::END-LOOP
          (RETURN-FROM NIL (CDR LOOP-LIST-HEAD-1541)))))))
;=>  (0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
;     29 30 31 32 33 34 35 36 37 38 39 40 41)

もしくは、ラベルを付けることで、参照がすっぽ抜けなくすれば良いので、*PRINT-GENSYM* はそのままで、 *PRINT-CIRCLE* をTに設定します。
その場合は、こんな具合

(BLOCK NIL
  (LET ((I 0))
    (DECLARE (TYPE NUMBER I))
    (LET ((#1=#:LOOP-REPEAT-1553 (CEILING 42)))
      (DECLARE (TYPE (MOD 43) #1#))
      (LET* ((#2=#:LOOP-LIST-HEAD-1554 (LIST NIL))
             (#3=#:LOOP-LIST-TAIL-1555 #2#))
        (TAGBODY
         SB-LOOP::NEXT-LOOP
          (IF (<= #1# 0)
              (GO SB-LOOP::END-LOOP)
              (LET* ((#4=#:G1557 1) (#5=#:NEW1556 (- #1# #4#)))
                (SETQ #1# #5#)))
          (RPLACD #3# (SETQ #3# (LIST I)))
          (SETQ I (1+ I))
          (GO SB-LOOP::NEXT-LOOP)
         SB-LOOP::END-LOOP
          (RETURN-FROM NIL (CDR #2#)))))))
;=>  (0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
;     29 30 31 32 33 34 35 36 37 38 39 40 41)

まあ、展開をいじることを考えれば、 *PRINT-GENSYM* をNILにする方が便利でしょうか。

 この為に大域変数をいじるのは嫌だなあというところですが、SLIMEのマクロ展開では、スペシャル変数のバインディングを、 swank::*macroexpand-printer-bindings* で制御できます。
ということで、

(defun mexp-string (form)
  (let ((swank::*macroexpand-printer-bindings*
          (List* '(*print-gensym*)
                 swank::*macroexpand-printer-bindings*)))
    (swank:swank-macroexpand-all form)))

こんな具合の関数を作って、Emacs側からは、

(defun slime-macroexpand-all-foo ()
  (interactive)
  (slime-eval-macroexpand 'MEXP-STRING))

こんな感じのコマンドを作ってやれば、 slime-macroexpand-all-foo でカスタマイズされた展開結果を得ることが可能です。


HTML generated by 3bmd in SBCL 1.2.7

アナフォリックマクロのITの競合対策

Posted 2015-01-06 16:21:33 GMT

 自分は、アナフォリックマクロはあまり好きではなく、全然使わないのでどうでも良いところではあるのですが、アナフォリックマクロのライブラリを混ぜて使う際に起きるitの競合は、一体どういう風に解決できるのかについては興味があるので色々と考えてみたりしています。

 どんな問題かというと、簡単なコード例としては、

(cl:in-package cl-user)

(Defpackage :foo (:use) (:Export :aif :it))

(Defpackage :bar (:use) (:Export :aif :it))

(Defmacro foo:aif (pred con &Optional alt) `(Let ((foo:it ,pred)) (If foo:it ,con ,alt)))

(Defmacro bar:aif (pred con &Optional alt) `(Let ((bar:it ,pred)) (If bar:it ,con ,alt)))

(foo:aif :foo it) ;!> The variable IT is unbound.

(bar:aif :foo it) ;!> The variable IT is unbound.

こんな感じです。
この場合、itのシンボルが3つのパッケージに存在しますが、これが一つにまとまらないと意図した挙動になりません。

パッケージを多重定義してシンボルをまとめる

 一つの解決案としては、a、b、cというアナフォリックマクロを提供するパッケージがあったとして、ライブラリがコンパイルしてロードされる前に、a、b、cのパッケージを作ってしまい、そこに共通のitをインポートして置いた後にライブラリをロードする、という方法を考えました。

 大抵の処理系では上手く行くのですが、ABCLだと先にdefpackageした内容と後で定義したdefpackageの内容をマージしないようなので、上手く行きません。
まあ、defpackageの内容がマージされるというのも処理系依存の動作のようなので、ABCLの動作でも問題なさそうです。

HyperSpec: defpackage

If defined-package-name already refers to an existing package, the
name-to-package mapping for that name is not changed. If the new
definition is at variance with the current state of that package, the
consequences are undefined; an implementation might choose to modify
the existing package to reflect the new definition. If
defined-package-name is a symbol, its name is used.

シンボルマクロを使ってitを切り換える

 他に何か方法はないか考えてみましたが、シンボルマクロを使うのはどうかなと思って試してみました。

(Define-Symbol-Macro it (progv '(#0=#:it) '()
                          (Macrolet ((it-exisist-p (sym &Environment env)
                                       (And (Eq :lexical
                                                #+sbcl (sb-cltl2:variable-information sym env)
                                                #+lispworks (hcl:variable-information sym env))
                                            sym)))
                            (Or (it-exisist-p foo:it)
                                (it-exisist-p bar:it)
                                (it-exisist-p kl:it)
                                #0#))))

(foo:aif :foo it 'alt) ;=> :FOO

(kl:aif :foo it 'alt) ;=> :FOO

(Defun foo (x) (kl:aand x (foo:aif it (* 42 it))))

(foo 42) ;=> 1764

 何をしているかというと、variable-informationでitという名前のレキシカル環境の束縛があるかどうか確かめて、そのパッケージのitを返すという、割合に無理矢理なものです。
progvが異様ですが、空振りした際に返すシンボルの為で、コンパイル時に警告が出ないようにスペシャルにしつつ未束縛なローカル変数を作るとなるとprogvしかないようです。

 上記の動作例をみる限りでは上手く行っているように見えますが、こんな落とし穴があります。

(Let ((it 42))
  (foo:aif :foo
          it
          'alt))
;=>  42

つまり、変数束縛があると、先に解決されてしまう訳ですね。無念。

まとめ

 アナフォリックマクロのitの競合について考えてみましたが、なかなか難しいようです。
マクロの作り方としては、展開先のitを使うようにすれば良いかなとは思いますが、既存のライブラリでそういうことをしているものは、ほぼ0なので、混ぜて使うのはなかなか厄介ということになります。

 ちなみにですが、Schemeのsyntax-caseだと展開先の識別子を使うようなので、こういう問題はないようです。


(library aif-a
  (export aif)
  (import (rnrs))

(define-syntax aif (lambda (x) (syntax-case x () ((_ test then else) (with-syntax ((it (datum->syntax #'x 'it))) #'(let ((it test)) (if it then else))))))))

(library aif-b (export bif) (import (rnrs))

(define-syntax bif (lambda (x) (syntax-case x () ((_ test then else) (with-syntax ((it (datum->syntax #'x 'it))) #'(let ((it test)) (if it then else))))))))

こんな感じに書いて、使う先でimportするのですが、競合もなく動くようです。
これで良いのか自信はないですが、これで正しいなら、syntax-caseは便利ですね。


HTML generated by 3bmd in LispWorks Personal Edition 4.4.6

Symbolicsではパッケージ名を一番短かいもので表示しているではないか

Posted 2015-01-04 09:05:32 GMT

 あまりCLパッケージをuseしてない状態のパッケージで作業することは、普通はないと思いますが、自分はLisp方言のコンパチパッケージを作って遊んでいるので、そういうことが良くあります。
そういう場合に非常にウザいのが、マクロを展開した時にcommon-lispパッケージが長々と表示されることです。
例えばこんな具合

(Let ((*Package* (Find-Package :Keyword)))
  (Pprint
   (macroexpand-all
    '(Defun foo (n)
      (Let ((x 42))
        (+ n x))))))
;>>  
;>>  (COMMON-LISP:PROGN (CCL::%DEFUN (CCL:NFUNCTION COMMON-LISP-USER::FOO
;>>                                                 COMMON-LISP:LAMBDA
;>>                                                 (COMMON-LISP-USER::N)
;>>                                                 (COMMON-LISP:DECLARE
;>>                                                  (CCL::GLOBAL-FUNCTION-NAME
;>>                                                   COMMON-LISP-USER::FOO))
;>>                                                 (COMMON-LISP:BLOCK
;>>                                                  COMMON-LISP-USER::FOO
;>>                                                  (COMMON-LISP:LET
;>>                                                   ((COMMON-LISP-USER::X 42))
;>>                                                   (COMMON-LISP:+
;>>                                                    COMMON-LISP-USER::N
;>>                                                    COMMON-LISP-USER::X))))
;>>                                  'COMMON-LISP:NIL)
;>>                     'COMMON-LISP-USER::FOO)
;=>  <no values>

こんな感じなので、マクロのデバッグなどでは、ごちゃごちゃしすぎてなんだか良く分からなくなります。

Symbolics Common Lispでは一番短かいパッケージ名で表示するのがデフォルト

 これを嫌って自分は、SBCLの内部のoutput-symbolを改造して、一番短い名前が表示されるようにしてみていましたが、Open GeneraのSymbolics CLを触っていたら、なんと一番短かい名前で表示されるではないですか。
もしやと思って、TI-Explorerでも確認してみましたが、同じ挙動。
Common Lispが稼動するMIT系Lispマシンではこの挙動なのかもしれません。
ちなみに、同じ長さの場合を試してみましたが、この場合は、package-nicknamesリストの順番のようです。

 やっぱり、一番短かいパッケージ名で表示されるべきだろうという感が強まったので、パッケージにまとめて、SBCL以外にも対応してみることにしました。

package-name-shortenerをロードするとこんな感じになります。

(ql:quickload :package-name-shortener)
;>>  To load "package-name-shortener":
;>>    Load 1 ASDF system:
;>>      package-name-shortener
;>>  ; Loading "package-name-shortener"
;>>  
;>>  
;=>  (:PACKAGE-NAME-SHORTENER)

(Let ((*Package* (Find-Package :Keyword))) (Pprint (macroexpand-all '(Defun foo (n) (Let ((x 42)) (+ n x)))))) ;>> ;>> (CL:PROGN (CCL::%DEFUN (CCL:NFUNCTION CL-USER::FOO ;>> CL:LAMBDA ;>> (CL-USER::N) ;>> (CL:DECLARE ;>> (CCL::GLOBAL-FUNCTION-NAME ;>> CL-USER::FOO)) ;>> (CL:BLOCK CL-USER::FOO ;>> (CL:LET ;>> ((CL-USER::X 42)) ;>> (CL:+ CL-USER::N CL-USER::X)))) ;>> 'CL:NIL) ;>> 'CL-USER::FOO) ;=> <no values>

しかし、SBCL以外には、CCLに対応したのみで面倒臭くなりました。

まとめ

 Lispマシンには細かい気遣いが溢れているなあと関心するばかりです。
ちなみに、上述のpackage-name-shortenerですが、もし使ってみる場合は、システムの心臓部を変更するので、壊れることを覚悟で使いましょう。


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

SRFI-118の紹介 & Common Lispへ移植

Posted 2015-01-02 15:00:00 GMT

LISP Library 1000 の368回目。
今回は、「Simple adjustable-size strings 」です。

 Schemeの文字列の長さは可変でないようですが、可変長文字列を導入しようという提案です。
なお、現在のステイタスはドラフトです。
現在APIについての議論がされていて、メーリングリストの議論を眺める限りAPIは変更されそうな気配があり、Common Lispへの移植は時期尚早な気がしています。

移植について

(Let* ((s (Make-Array 0
                      :element-type 'Character
                      :Fill-Pointer 0
                      :adjustable T))
       (res (srfi-118:string-append! s "bar" "baz" "zot")))
  (List (Eq s res)
        (String= s "barbazzot")))
;=>  (T T)

(Let* ((s (Make-Array 9 :element-type 'Character :initial-element #\_ :Fill-Pointer 9 :adjustable T)) (res (srfi-118:string-replace! s 3 6 "BAR"))) (List (Eq s res) (String= s "___BAR___"))) ;=> (T T)

 このSRFI中でもCommon Lispについて触れられていますが、Common Lispでは可変長の文字列のサポートはあるので、その辺の機能を使って適当に移植しました。

 API的には、可変長の文字列を入力にして、可変長の文字列が返るのかどうかは、関数名で明示した方が使い勝手は良さそうだなあという印象です。その辺りは今後決定されると思いますので、決定次第調整しようかなというところ。


HTML generated by 3bmd in LispWorks Personal Edition 6.1.1

Older entries (1897 remaining)