#:g1: frontpage

 

C++ STLにCommon Lispの影響はあるのかを探る旅(2)

Posted 2015-04-18 06:54:53 GMT

前回は、C++ STLにCommon Lispの影響はあるのかをSTLの歴史を遡って先祖にあたるAdaのリストライブラリを眺めてみました
今回は、C++ STLそのものについてですが、とりあえずC++ STLの作者によるレポートがあるので眺めてみます。

この中で、

accumulate is similar to the APL reduction operator and Common Lisp reduce function

という風にCommon Lispに触れてはいますが、影響を受けたようなことは特に記載されていません。

他にも何かないかなと探してみましたが、プレゼン資料でDylanやLispの良いとこ取りをしよう、的なことが書いてあるので、まあ影響があるといえばあるのかもしれません。

Dylanの名前が挙がっているのは若干意外ではありますが、DylanもCommon Lispの影響が非常に強く、シークエンス系に関しては、殆どそのままというか進化形みたいな所ではあります。

いまいちぱっとしませんが、とりあえず前回と同じく表にして眺めてみます。

C++ STL CLそのまま CLとは別名
accumulate REDUCE
adjacent_find
adjacent_difference
binary_search
copy COPY-SEQ
copy_backward
count COUNT
count_if COUNT-IF
equal EQUAL
equal_range
fill FILL
fill_n FILL :START :END
find FIND
find_end FIND :FROM-END T
find_first_of POSITION
find_if FIND-IF
for_each MAPC
genarate_n
generate MAP-INTO
includes SUBSETP ※リストのみ
inner_product
inplace_merge
iter_swap
lexicographical_compare
lower_bound
make_heap
max MAX ※REDUCEと組み合わせ
max_element
merge MERGE
min MIN
min_element
mismatch MISMATCH
next_permutation
nth_element ELT NTH
partial_sum
partial_sort
partial_sort_copy
partition
pop_heap
prev_permutation
push_heap
random_shuffle
remove REMOVE ※ DELETE
remove_copy REMOVE
remove_copy_if REMOVE-IF
remove_if REMOVE-IF ※ DELETE-IF
replace REPLACE
replace_copy
replace_copy_if
replace_if
reverse REVERSE ※ NREVERSE
reverse_copy REVERSE
rotate ROTATEF
rotate_copy
search SEARCH
search_n SEARCH :START1 :END1
set_difference SET-DIFFERENCE ※リストのみ
set_intersection INTERSECTION ※リストのみ
set_symmetric_difference
set_union UNION ※リストのみ
sort SORT
sort_heap
stable_sort stable-sort
stable_partition
swap ROTATEF
swap_ranges ROTATEF+SUBSEQ
transform MAP-INTO
unique DELETE-DUPLICATES
unique_copy REMOVE-DUPLICATES
upper_bound

Adaのリストライブラリ程、そのまんまではないですが、まあまあ機能と名前は被っている風に見えます。
全般的な所ですが、Adaと同じく、C++ STLも破壊的操作が基本のようで、元を変更しない場合には、_COPY 版が用意されているようです。
Common Lispはシークエンス全般ではなくリストに特化した関数の方が充実しているのが、LISPらしいというか古めかしい所。
シークエンス用関数に関しては、範囲指定が取り込まれているのでC++ STLより汎用的な面もあるようです。

まとめ

Stepanov氏の文献にはSchemeという名前は出てくるのですが、Common Lispの名前はあまり出て来ないのが残念です。
まあ、Lispの良いとこ取りをしよう、とプレゼンでも書いていますし、関数の名前と機能もかなり被っていますし、影響はありますいうことで良いのではないでしょうか。
この件について何か良い資料をご存知でしたら是非とも教えて下さい!


HTML generated by 3bmd in SBCL 1.2.10

C++ STLにCommon Lispの影響はあるのかを探る旅(1)

Posted 2015-04-15 17:40:35 GMT

自分は、C++のことは殆ど知らないのですが、関数名にremove_ifや、count_ifset_differenceがあるのを見て、これってCommon Lispに由来してるんじゃないのかなと思っていました。
しかし、C++のBoostは関数型言語に強い影響を受けているという話を耳にすることはあれ、C++ STLががCommon Lispに影響を受けているという話は耳にしたことが無い気がします。

適当にウェブをググってみたりしても、そんな話もないので、大元であるSTL作者の一人 Alexander A. Stepanov氏のWikipediaでの記述や、氏のウェブページを眺めてみたところ、STLの先祖は、Adaのリスト(シークエンス)ライブラリで、そのコンセプトの検証にSchemeが使われたらしいことが分かりました。

A library of generic algorithms in Adaの方には、リストライブラリは、Common Lispをベースにしているという記述もあります。
このページから、このリストライブラリもダウンロードできるので、暇潰しにどれだけCommon Lispに似ているか一覧を作ってみることにしました。

名前は同じものの動作が違っているものには、(※)を付けました。

Ada CLそのまま CLとは別名
Accumulate
Add_Current
Add_First PUSH
Add_Last
Advance POP
Append APPEND
Append_First_N
Attach_To_Tail
Butlast BUTLAST※
Butlast_Copy BUTLAST
Concatenate CONCATENATE※ NCONC
Concatenate_Copy CONCATENATE
Construct
Copy_First_N
Copy_Sequence COPY-SEQ
Count COUNT
Count_If COUNT-IF
Count_If_Not COUNT-IF-NOT
Create
Current
Delete DELETE
Delete_Copy REMOVE
Delete_Copy_Append
Delete_Copy_Duplicates REMOVE-DUPLICATES
Delete_Copy_Duplicates_Append
Delete_Copy_If REMOVE-IF
Delete_Copy_If_Not REMOVE-IF-NOT
Delete_Duplicates DELETE-DUPLICATES
Delete_If DELETE-IF
Delete_If_Not DELETE-IF-NOT
Drop_Head POP
Drop_Tail
Empty NULL
Equal EQUAL
Every EVERY
Find FIND
Find_If FIND-IF
Find_If_Not FIND-IF-NOT
First FIRST
For_Each MAPC
For_Each_2 MAPC
For_Each_Cell MAPL
For_Each_Cell_2 MAPL
Free
Free_Construct
Free_Sequence
Front
Full
Initialize
Invert
Invert_Copy
Invert_Partition
Is_Empty NULL
Is_End ENDP
Is_Not_End
Last LAST※
Length LENGTH
Make_Sequence MAKE-SEQUENCE
Map MAP
Map_2
Map_Copy
Map_Copy_2
Map_Copy_2_Append
Map_Copy_Append
Merge MERGE
Merge_Non_Empty
Mismatch MISMATCH
Next CDR REST
Not_Any NOTANY
Not_Every NOTEVERY
Nth NTH
Nth_Rest NTHCDR
Pop POP
Pop_Front
Position POSITION
Position_If POSITION-IF
Position_If_Not POSITION-IF-NOT
Push PUSH
Push_Front PUSH
Push_Rear
Rear
Reduce REDUCE
Reverse_Append REVAPPEND
Reverse_Concatenate NRECONC
Search SEARCH
Set_Current
Set_First (SETF FIRST) RPLACA
Set_Last
Set_Next (SETF REST) RPLACD
Set_Nth (SETF NTH)
Some SOME
Sort SORT
Split
Subsequence SUBSEQ
Substitute SUBSTITUTE
Substitute_Copy
Substitute_Copy_If
Substitute_Copy_If_Not
Substitute_If SUBSTITUTE-IF
Substitute_If_Not SUBSTITUTE-IF-NOT
Top

このリストでは、双方向リンクリストや、スタック、ベクタのための関数もあるのでCommon Lispのものと対応していないものも散見されますが、Common Lispもリストを含めてシークエンスに関しては、genericな関数が多いので大体同じです。
主な違いとしては、このAdaのライブラリは、副作用ありの処理が標準となっていて、名前もそちらが標準という感じで、コピーを作るものは、なんとか_Copyという名前が付いていることでしょうか。

この表をみる限りでは、Common LispのシークエンスライブラリをAdaに移植したといっても良いような気さえします。

さて、後継のC++ STLではどうなっているのか。続けられたら続きます…。


HTML generated by 3bmd in SBCL 1.2.10

DEFSTRUCTでリストのアクセサをまとめて定義する

Posted 2015-04-12 23:36:59 GMT

defstruct:typeの指定でリストをバックエンドにした構造が定義できますが、この機能を活用することで、リストで構造を定義してしまったコードにまとめてアクセサを作ったり、後々構造体に書き換えたりするのを楽にすることが可能です。
例えば、

(defun create-character (hp mp str vit dex agi atc def buf items)
  (list hp mp str vit dex agi atc def buf items))

(defvar *spopo* (create-character 10 10 10 10 10 10 10 10 10 '("dagger")))

(defun get-vit (chr) (nth 3 chr))

こんな感じにとりあえずで書き始めてしまって、アクセサを作るのが大変だなあとなった場合でも、

(defstruct (chr (:type list)
                (:constructor create-chr (hp mp str vit dex agi atc def buf items))
                (:conc-name "GET-"))
  hp mp str vit dex agi atc def buf items)

(create-chr 10 10 10 10 10 10 10 10 10 '("dagger")) ;=> (10 10 10 10 10 10 10 10 10 ("dagger")) (get-dex *spopo*) ;=> 10

こんな風にdefstructで構造を定義してやることで、アクセサの自動定義の恩恵に与ることができます。

また、場合によっては、リストの部分構造を別途定義することも可能です。

(defstruct (chr-sub-params (:type list)
                           (:initial-offset 2)
                           (:conc-name "GET-SUB-PARAM-"))
  str vit dex agi atc def buf)

(*:iota 10) ;=> (0 1 2 3 4 5 6 7 8 9)

(get-str (*:iota 10)) ;=> 2

(get-sub-param-agi *spopo*) ;=> 10

まとめ

defstructがアクセサを自動定義することについては長短両所がありますが、便利なこともままあります。

ちなみに、1960-70年代のLISPにはデータ構造がリスト位しかなかったため、car、cdr、cadaddr…が連発するコードが多かったようです。
この傾向は、Common Lispが登場してリスト以外の構造の利用が進む1980年中盤位まで続きますが、defstructのリスト関係のオプションが充実しているのも、この辺りの事情がある気がしています。


HTML generated by 3bmd in SBCL 1.2.10

DEFSTRUCTで文字列を扱う

Posted 2015-04-12 23:22:38 GMT

defstruct:typeの指定によりstructure-object以外にもリストやベクタの構造も定義可能ですが、ベクタの方は、処理系依存でベクタのサブタイプを指定することが可能です。

どんな感じかというと、大抵の処理系では、こんな具合です。

(defstruct (milwa (:type (vector character)))
  (x #\Space)
  (y #\Space)
  (z #\Space))

(make-milwa) ;=> " "

(make-milwa :x #\0 :y #\1 :z #\2) ;=> "012"

(milwa-x "012") ;=> #\0

HyperSpecには、

type---one of the type specifiers list, vector, or (vector size), or some other type specifier defined by the implementation to be appropriate.

とあるので、(vector size)と指定してしまいそうですが、大抵の処理系では、(vector type) のようです。
定義からして長さは有限で自明な気もするのでHyperSpecの説明は不思議ではありますね。
まあ、some other type specifierの方を大抵の処理系が採用しているということなのでしょう。

まとめ

DEFSTRUCTで文字列を扱う方法を紹介してみました。
そんなのどこで使うのという思いはあります。


HTML generated by 3bmd in SBCL 1.2.10

Eclipse Common Lisp のソースコードが公開されたのでビルドしてみる

Posted 2015-04-08 04:29:01 GMT

 最近、ぼんやりする日々ですが、Twitterを眺めていた所、Eclipse Common Lisp のソースコードがGitHubで公開されたとのニュース!
個人的には、知る人ぞ知る謎の処理系 No. 1だったので、早速GitHbからコードをチェックアウトしてビルドに挑戦してみました。

Eclipse Common Lisp とは

その前に、Eclipse Common Lispとは何かということですが、Eclipse Common Lispは、Elwood Corporationが1997年に発売したLispからCへのトランスレータ方式のCommon Lisp処理系で、

  • ANSI Common Lisp 準拠
  • MOP装備
  • Common LispのプログラムをCのコードとして出力でき、CからCommon Lispを呼んだり、Common LispからCを呼んだりできる
  • Unicode対応
  • お値段 $300 と商用処理系としては非常に安価(後に$500、処理系ソース付きで$5,000)

等々が特長の処理系です。
Cへの変換というとKCL系統(GCL、ECL等々)が有名ですが、Eclipse CLは、新規に開発されたもので、KCLとは全く別物になります。

1997年に最初のバージョン1.0のアナウンスがされ、

1998年に、バージョン1.1のアナウンスもされましたが、

しかし、その後、残念ながら、Elwood Corporation はCommon Lisp事業から撤退したのか、それ切りになってしまったようです。

私が、Eclipse CL について知ったのは、Eclipse CLのプロジェクトリーダーであった、Howard Stearns氏が自身のウェブページにマニュアルを掲載したのが切っ掛けでしたが、このような処理系があったことと、開発が継続されなかったことを残念に思っていました。

MOP装備のANSI Common Lisp 処理系は一人で作れる(らしい)

Howard Stearns氏の経歴のページによれば、ANSI Common Lisp処理系を丸ごと作ったと書いてあるので、どうも一人でこの処理系を作ったのではないかと思われます。

ソースを眺めてみてもそんな雰囲気です。恐しや!!

眺めた所では、どうやら、Eclipse CLの処理系のCのソースはCommon Lispで生成しているようで、それには、Allegro CL 4.3が主に使われていたようです。

ビルドしてみる

さて、早速ビルドに挑戦してみましょう。まず、

から、コードを拾ってきます。
他に必要なものとしては、libgc(Boehm GC)が必要になります。
Linux等では、パッケージでインストールできることが殆どだと思います。
git cloneして、

$ cd eclipse-lisp/c
$ make

これだけでビルドできちゃうのか!と思いましたが、Blake McBride氏がいまどきのOSでも動くようにファイルシステム周り等を修正してくれているようです。ありがたや!!

さてmakeするとeclipseというバイナリができますが、これが処理系の本体です。

$ eclipse-lisp/c/eclipse
 Initializing: intern-ext intern-int intern-add list kernel mop-init types-run control-run symbol clases clos-run clos-define clos-seq common mop class-meth methods gfunc method-init predicates arithmetic conv hash type-ops type-seq method-comb type-mops sequence seq-mod search sort control numbers trig num-conv array string struct-run character tree package c-pkg resource env condition set alist bit-array bignum bits equalp stream file-stream comp-stream circle init reader printer format pretty print-object doc describer miscel random pathname file enclose dispatch parameters pkg signals 
 dev-meth env-comp common-comp prog-comp cont-comp cont-comp2 macro macro-comp opts number-comp list-comp clos-comp pretty-comp struct-comp cond-comp more-compile env-comp loop directives describe debug default-pp walk-util walk-top walk-special literal evaluation c file-walk c-walk prim-decs 

Eclipse Common Lisp Version 1.1 Copyright (c) 1997, 1998 Elwood Corporation, Oak Creek, WI, USA.

Lisp Top Level.

Options: 0: Print help. [:HELP] 1: Exit Lisp. [:EXIT] >

こんな感じの起動画面です。

ちょっと古いOSでもビルドを試す

上記では、makeだけでしたが、本来は、make installするようです。これも試してみましたが、こちらは、まだ対応していない様子。
一応想定している処理を探って環境を合せてみましたが、libgc5.0のソースをeclipse-lisp/c/gc以下に展開すれば、make installが上手く行くようです。
ちなみに、試したOSは、Knoppix (Kernel 2.4.27) ですが、こっちの方が若干調子が良いような気がしないでもありません。
しかし、まだ違いを詳しくは調べていません。

fib を定義してみよう

さて、おなじみのfibを定義してみます。

(defun fib (n)
  (if (< n 2)
      n
      (+ (fib (1- n)) (fib (- n 2)))))

ちなみに改行が入っているとセグフォしたりするので、まだ色々不安定なようです。

(disassemble 'fib)

すると、

#include <eclipse.h>

clObject cl1MINUS_FUNC(clProto),
  clCharpSimpleBaseString __P((clCharp)), clExtraArgs(clProto),
  clIntern(clProto), clMissingArgs(clProto), clPkg(clProto);

extern clObject clADD, clLT, clSUBT;

static clObject I_1, I_2, PKG_USER, STR_FIB__0, STR_USER__1, usrFIB;

clObject usrFib clVdecl(_ap) { clObject n; { clBeginParse(_ap); clSetq(n, (_clVp(_ap) ? clVpop(_ap) : clMissingArgs(I_1, clEOA))); if (_clVp(_ap)) clExtraArgs(clVargs(_ap), clEOA); clEndParse(_ap); } { clObject L_test; { clObject L_0; clSetq(L_0, n); clSetq(L_test, clFuncallFunction(clSymbolFunctionValue(clLT), L_0, I_2, clEOA)); } if (clTrue(L_test)) return(clValues1(n)); else { clObject L_1, L_0; { clObject L_0__R1; { clObject L_0__R2; clSetq(L_0__R2, n); clSetq(L_0__R1, cl1MINUS_FUNC(L_0__R2, clEOA)); } clSetq(L_0, clFuncallFunction(clFdefinition(usrFIB, clEOA), L_0__R1, clEOA)); } { clObject L_0__R1; { clObject L_0__R2; clSetq(L_0__R2, n); clSetq(L_0__R1, clFuncallFunction(clSymbolFunctionValue(clSUBT), L_0__R2, I_2, clEOA)); } clSetq(L_1, clFuncallFunction(clFdefinition(usrFIB, clEOA), L_0__R1, clEOA)); } return(clFuncallFunction(clSymbolFunctionValue(clADD), L_0, L_1, clEOA)); } } }

void clLoader __P((void)) { clDbind(clstarPACKAGEstar); clDbind(clstarREADTABLEstar); clDbind(clstarLOAD_TRUENAMEstar); clDbind(clstarLOAD_PATHNAMEstar); clSetq(I_1, clIntFixnum(1)); clSetq(I_2, clIntFixnum(2)); clSetq(STR_FIB__0, clCharpSimpleBaseString("FIB")); clSetq(STR_USER__1, clCharpSimpleBaseString("USER")); clSetq(PKG_USER, clPkg(STR_USER__1, clEOA)); clSetq(usrFIB, clIntern(STR_FIB__0, PKG_USER, clEOA));

clMakeClosure(0, usrFib, clNULL_HOOK); clUnwind(4); }

こんな感じのものが出力されます。

Eclipse CL の日本語の扱い

Eclipse CLのマニュアルには、UCS-4が使えるということなのですが、具体的にはどうなっているのか確認してみました。

とりあえず、

char-code-limit
;=> 1114112

なので、確かにUCS-4の範囲はカバーしています。
"おはよう日本!"という文字列を文字コードのリストにすると、(12362 12399 12424 12358 26085 26412 33)になりますが、

(map 'list #'char-code
     (map 'string #'code-char '(12362 12399 12424 12358 26085 26412 33)))
;=>  (12362 12399 12424 12358 26085 26412 33)

という風に、リスト→文字列→リスト、は元に戻ります。

しかし、エンコードは一体何を使っていることになってるのかということで、ファイルに書き出してみました。
ASCIIの範囲外で書き出したい場合(UCS-2/4で書き出したい場合)は、:external-formatには、:ucsを指定します。

(with-open-file (out "ohayo.txt" :direction :output :if-exists :supersede
                     :external-format :ucs)
  (map nil (lambda (c) (princ (code-char c) out)) '(12362 12399 12424 12358 26085 26412 33))

nil)

の結果として、ohayo.txtの中身は、

00000000: 4a30 0000 6f30 0000 8830 0000 4630 0000  J0..o0...0..F0..
00000010: e565 0000 2c67 0000 2100 0000            .e..,g..!...

となっていました。

4e300000をひっくり返すと、0000304a => 12362(お)なのでリトルエンディアンのようです。
ということで、SBCLからファイルを読んでみましたが、

(with-open-file (in "ohayo.txt" :element-type '(unsigned-byte 8))
  (let* ((len (file-length in))
         (seq (make-array len :element-type '(unsigned-byte 8))))
    (read-sequence seq in)
    (babel:octets-to-string seq :encoding :utf-32le)))
;=>  "おはよう日本!"

utf-32leとして読めることは読めるようです。
2000年辺りは、まだ多言語対応も揺れていた時期かなと思いますが、ゴージャスにUCS-4採用に舵を切ったCommon Lisp処理系は、Eclipse CL位かなと思います。

みつけたバグ

with-hash-table-iterator

ハッシュテーブルのイテレータの実装に使われている内部関数がどういう訳か実装されていません。
コードはあるのですが、他のホスト処理系からクロスコンパイルする場合に使われるもののようです。
とはいえ、maphash等は動くので、with-hash-table-iteratorのコードが何故この状態なのかは謎です。
この影響として、with-hash-table-iteratorを利用する、with-package-iteratorや、更にそれを利用する、do-symbols等が動きません。

とりあえず、do-symbols位なら、

(defmacro do-symbols ((s pkg &optional result) &body body)
  `(progn
     (let ((pkg (find-package ,pkg)))
       (when pkg
         (maphash (lambda (k ,s)
                (declare (ignore k))
                    ,@body)
                  (slot-value (find-package ,pkg) 'eclipse::externals))
         (maphash (lambda (k ,s)
                    (declare (ignore k))
                    ,@body)
                  (slot-value (find-package ,pkg) 'eclipse::internals))))
     ,result))

でしのげます。
この辺りは、クラスを多用しているのが結構特徴的というか、ANSI CL制定後にスクラッチで書かれた処理系ならではという感じはします。

defpackage

:internオプションが使えません。

(defpackage :mamorlis
  (:use)
  (:intern :a :b :c))

が、

(EVAL-WHEN (:COMPILE-TOPLEVEL :LOAD-TOPLEVEL :EXECUTE)
  (LET ((#:PKG168379 (FIND-PACKAGE "MAMORLIS")))
    (IF #:PKG168379 (RENAME-PACKAGE #:PKG168379 "MAMORLIS" 'NIL)
     (SETQ #:PKG168379
           (MAKE-PACKAGE "MAMORLIS"
                         :NICKNAMES
                         'NIL
                         :USE
                         NIL
                         'ECLIPSE::INTERNAL-SIZE
                         211
                         'ECLIPSE::EXTERNAL-SIZE
                         0)))
    (INTERN '("C" "B" "A") #:PKG168379)
    #:PKG168379))

に展開されるので、intern にリストが渡ってエラーになるようです。
defpackage:internオプションはあまり使うこともないので、バグが埋もれていたのではないかと思い ますが、偶然見付けてしまいました。
こんな所もにまたなんとなく、フルスクラッチで書かれたんだなあということを感じてしまいます。

まとめ

 等々、まとまりもなく書き散らかしてしまいましたが、Eclipse CLの詳細は付属のマニュアルに書かれていますので、そちらを参照してみてください。
Eclipse CLは非常にポテンシャルがある処理系だと思っているので、今後開発が有志によって進んで行くことを期待しています!


HTML generated by 3bmd in SBCL 1.2.10

スペシャル変数のエイリアスを作る

Posted 2015-04-06 05:20:34 GMT

 以前、SBCLでAllegro CLのモダンモードを再現しようとしてみたのですが、結局Common Lispの枠内でどうにかするのは無理という結論になりました。
作戦としては、COMMON-LISPパッケージ内のシンボル名を小文字にしたcommon-lispパッケージをつくるというものでしたが、関数やマクロは元の関数を参照するエイリアスを作成できるものの、スペシャル変数がどうにも無理です。
しょうがないので、とりあえず、*をリーダーマクロにして、*で囲まれた名前は対応するCommon Lispのシンボルに解決されるという無理目なものをでっちあげたことがありました。

 そんなCommon Lispですが、Lispマシンのマニュアルを眺めるとスペシャル変数のエイリアスを作成する方法があるので、Open Generaで試してみました。
forward-value-cellというのを使います。

forward-value-cellを使うと、こんなことができてしまいます。

(let ((*read-base* 36.))
  (read-from-string "AA"))
;=> 370

(defvar *rb*)

;;; *rb* を *read-base* のエイリアスとする (scl:forward-value-cell '*read-base* '*rb*)

(let ((*rb* 36.)) (list (read-from-string "AA") *read-base*)) ;=> (370 36)

なんじゃこりゃという動作ですが、スペシャル変数の挙動からすると、こうならないと役目は果せないわけですね。

 何故こういうものが用意されているのかですが、良く使われる*standard-output*のような変数は、Common Lisp以前には、standard-outputのように耳当てなしでした。
この辺りを同じシステムで共存させる上で、こういうものが必要になったのではないかと想像しています。
とはいえ、この手の仕組みは Forwarding としてまとめて解説されているので、invisible pointer の応用例の一つなだけかもしれません。

まとめ

Lispマシンの、invisible pointer の活用例としては他にも妙なものが沢山ありますが、そのうちまとめてみたい所です。


HTML generated by 3bmd in SBCL 1.2.10

SBCLのコンパイルメッセージをカスタマイズする

Posted 2015-03-31 15:10:54 GMT

 前回色々な処理系のコンパイルメッセージを眺めましたが、色々な表現方法があることを知ると今度はカスタマイズもしてみたくなります。

 ということで、普段一番良く使うSBCLをカスタマイズしてみようと処理系内部を眺めてみましたが、残念ながらユーザーがカスタマイズできるようなフックは用意されていません。
いじりやすそうなところを探してみましたが、sb-c::note-top-level-formが割合にいじり甲斐がありそうなので、ここをいじってみることにしました。

sb-c::note-top-level-form をいじる

sb-c::note-top-level-formはコンパイルしているトップレベルのフォームを表示している所です。 ここを若干VAX LISP風にしてみます。

(sb-ext:without-package-locks
 sb-c::
 (defun note-top-level-form (form &optional finalp)
   (when *compile-print*
     (cond ((not *top-level-form-noted*)
            (let ((*print-length* 2)
                  (*print-level* 2)
                  (*print-pretty* nil)
                  (*package* sb-int::*keyword-package*)
                  (fctn (if (consp form) (second form) form))
                  (op (if (consp form) (first form) form)))
              (with-compiler-io-syntax
                  (case op
                    ((cl:defun cl:defmacro)
                     (compiler-mumble
                      #-sb-xc-host "~&~S ~:[compiled~;converted~]."
                      #+sb-xc-host "~&; ~:[x-compiling~;x-converting~] ~S"
                      fctn *block-compile*))
                    (otherwise
                     ))))
            form)
           ((and finalp
                 (eq :top-level-forms *compile-print*)
                 (neq form *top-level-form-noted*))
            (let ((*print-length* 1)
                  (*print-level* 1)
                  (*print-pretty* nil))
              (with-compiler-io-syntax
                  (compiler-mumble "~&; ... top level ~S" form)))
            form)
           (t
            *top-level-form-noted*)))))

変更したのは、

  1. フォームの表示方式の変更
  2. オペレーター名のシンボルをパッケージ名付きで印字
  3. 表示するのは、defundefmacroのみ

という所です。

 これで

(defun foo (x y z)
  (list x y z))

をコンパイルすると

CL-USER::FOO compiled.

という感じになります。

 特に意図した訳でもなかったのですが、表示がパッケージ名付きのシンボルになるとデバッグ時にM-.が効くので、非常に便利ということが分かりました。
これだけでもカスタマイズする価値はある感じなので、

(sb-ext:without-package-locks
 sb-c::
 (defun note-top-level-form (form &optional finalp)
   (when *compile-print*
     (cond ((not *top-level-form-noted*)
            (let ((*print-length* 2)
                  (*print-level* 2)
                  (*print-pretty* nil)
                  (*package* sb-int:*keyword-package*))
              (with-compiler-io-syntax
                  (compiler-mumble
                   #-sb-xc-host "~&; ~:[compiling~;converting~] ~S"
                   #+sb-xc-host "~&; ~:[x-compiling~;x-converting~] ~S"
                   *block-compile* form)))
            form)
           ((and finalp
                 (eq :top-level-forms *compile-print*)
                 (neq form *top-level-form-noted*))
            (let ((*print-length* 1)
                  (*print-level* 1)
                  (*print-pretty* nil))
              (with-compiler-io-syntax
                  (compiler-mumble "~&; ... top level ~S" form)))
            form)
           (t
            *top-level-form-noted*)))))

こういうのでも良いかもしれません。
これで、

; compiling (CL:DEFUN CL-USER::FOO ...)

になります。
ちなみに、デフォルトは、

; compiling (DEFUN FOO ...)

です。

 また、*print-pretty*やら*print-length*を任意に設定すれば、コンパイルしているフォームを丸ごと表示できたりもします。

; compiling (CL:DEFUN CL-USER::FOO (CL-USER::X CL-USER::Y CL-USER::Z)
              (CL:LIST CL-USER::X CL-USER::Y CL-USER::Z))

使えそうな使えなさそうな…。

まとめ

コンパイルメッセージに限りませんが、M-.が使えそうな所は、できるだけ効くようにしておくとデバッグの時に非常に快適です。


HTML generated by 3bmd in SBCL 1.2.10

Common Lisp処理系のコンパイルメッセージを眺める

Posted 2015-03-30 15:21:07 GMT

コンパイルするとずらずらと流れるメッセージですが、あまり気を付けて眺めたこともないなと思ったので、処理系ごとに比較してみたりすることにしました。 比較してみると、案外、処理系毎に違っているみたいです。

比較に使用するファイル

比較に使うファイルですが、コンパイル時のメッセージで良く見掛けそうなメッセージを出したいので、

  • 普通の問題ない、defvar、defun
  • 未定義関数の参照ありのdefun
  • 未定義変数ありのdefun
  • 未使用変数ありのdefun

位を用意してみました。
内容はこんな感じです。

(Defvar *a* 1)

(Defun a ())

(Defun d () (z))

(Defun dd () (zz))

(Defun b (n))

(Defun c () n)

(defun zz ())

;;; *EOF*

  • 関数DはZという未定義関数を含む
  • 関数DDはZZという未定義関数を含むが、ZZはファイルの後でZZの定義あり
  • 関数BはNという変数を未使用
  • 関数Cは、Nという未定義変数を参照

というところです。

以下、だらだらと記載が続きますので、興味のある所だけどうぞ。

LispWorks

まずはLispWorksですが、非常に簡素です。

;;; Compiling file cmsg.lsp ...
;;; Safety = 3, Speed = 1, Space = 1, Float = 1, Interruptible = 1
;;; Compilation speed = 1, Debug = 2, Fixnum safety = 3
;;; Source level debugging is on
;;; Source file recording is  on
;;; Cross referencing is on
; (TOP-LEVEL-FORM 3)

これだけ。 これだとコンパイルの状況が、あまり良く分からないですが、しかし、SLIMEと組み合せて使っていると、もっと色々警告も拾えている気がするので何か設定があるのかもしれません(:verbose はTにしていますが)。

SBCL

次にお馴染のSBCL。

普通の問題ない、defvar、defun

; compiling (DEFVAR *A* ...)
; compiling (DEFUN A ...)
; compiling (DEFUN D ...)
; compiling (DEFUN DD ...)

通常のフォームのコンパイルでは、こんな感じに表示されます。
どうもソースを眺めてみたところトップレベルのフォームを、

(let ((*print-length* 2))
  (print '(defun foo (n) n)))
;>> (DEFUN FOO ...)

して表示してるようです。なかなか上手い使い方ですね。

未定義関数の参照ありのdefun

ファイル処理中は未定義関数の参照については情報が出ません。

未定義変数ありのdefun

ファイル処理中は未定義変数の参照については情報が出ません。

未使用変数ありのdefun

; compiling (DEFUN B ...)
; file: cmsg.lsp
; in: DEFUN B
;     (DEFUN B (N))
; --> PROGN SB-IMPL::%DEFUN SB-IMPL::%DEFUN SB-INT:NAMED-LAMBDA 
; ==>
;   #'(SB-INT:NAMED-LAMBDA B
;         (N)
;       (BLOCK B))
; 
; caught STYLE-WARNING:
;   The variable N is defined but never used.

未使用変数については、フォームの処理中にメッセージがでます。

コンパイル単位の終了

; file: cmsg.lsp
; in: DEFUN C
;     (DEFUN C () N)
; --> PROGN SB-IMPL::%DEFUN SB-IMPL::%DEFUN SB-INT:NAMED-LAMBDA FUNCTION 
; ==>
;   (BLOCK C N)
; 
; caught WARNING:
;   undefined variable: N

; in: DEFUN D
;     (Z)
; 
; caught STYLE-WARNING:
;   undefined function: Z
; 
; compilation unit finished
;   Undefined function:
;     Z
;   Undefined variable:
;     N
;   caught 1 WARNING condition
;   caught 2 STYLE-WARNING conditions

; cmsg.fasl written ; compilation finished in 0:00:00.008

未定義変数と関数については、コンパイル単位の終了時にどさっと出てきます。
よく考えてみれば、ファイルが処理し終わるまで確定できないので最後に出すしかないのですが、これだと発生した場所から離れてしまうことになります。
コンパイル単位が長くなれば、それだけ離れてしまいますが、この辺りに謎のメッセージ感を感じてしまうのかもしれません。

ABCL

次にABCL。全体的にSBCLを参考にしている処理系なのでSBCLにほぼ同じですが、未定義変数はスペシャル変数とみなされます。

普通の問題ない、defvar、defun

; (DEFUN D ...)
; (DEFUN DD ...)

未定義関数の参照ありのdefun

ファイル処理中は未定義関数の参照については情報が出ません。

未定義変数ありのdefun

; (DEFUN C ...)
; in (DEFUN C ...)

; Caught STYLE-WARNING:
;   Undefined variable N assumed special

未使用変数ありのdefun

; (DEFUN B ...)
; in (DEFUN B ...)

; Caught STYLE-WARNING:
;   The variable N is defined but never used.

コンパイル単位の終了

; Compilation unit finished
;   Caught 2 STYLE-WARNING conditions
;   The following functions were used but not defined:
;     Z

; Wrote cmsg.abcl (0.111 seconds)

そしてコンパイル単位の終了時に未定義関数の警告

Allegro CL

つぎにAllegro CLですが、トップレベルフォームの処理状況は表示されません。
そして、未定義変数はスペシャル変数扱い。

普通の問題ない、defvar、defun

表示なし

未定義関数の参照ありのdefun

ファイル処理中は未定義関数の参照については情報が出ません。

未定義変数ありのdefun

; While compiling C:
Warning: Free reference to undeclared variable N assumed special.

未使用変数ありのdefun

; While compiling B:
Warning: Variable N is never used.

コンパイル単位の終了

;;; Fasl write complete
Warning: While compiling these undefined functions were referenced:
         Z from character position 1936 in cmsg.lsp

コンパイル単位の終了で、未定義関数の警告が出ます。

Clozure CL

次にClozure CL。未使用変数は処理時点で警告が出ますがスペシャル変数にはしないようです。

普通の問題ない、defvar、defun

トップレベルフォームは処理時に表示されません。

未定義関数の参照ありのdefun

ファイル処理中は未定義関数の参照については情報が出ません。

未定義変数ありのdefun

;   In C: Undeclared free variable N

未使用変数ありのdefun

;Compiler warnings for "cmsg.lsp" :
;   In B: Unused lexical variable N

コンパイル単位の終了

;Compiler warnings for "cmsg.lsp" :
;   In D: Undefined function Z

そして、コンパイル単位の終了で、未定義関数の警告が出ます。

CMUCL

次にCMUCL。自分的には、未定義変数はスペシャル変数にする印象がありましたが、トップレベルでsetqをするとスペシャル変数にするのとごっちゃになっていたようで、未定義変数をスペシャルにする訳ではありませんでした。やはり調べてみるものです。
基本的にCMUCLからフォークしたSBCLをシンプルにした感じです。しかし、シンプルではありますが、縦に長い表示。

普通の問題ない、defvar、defun

; Converted C.
; Compiling DEFUN C: 

のような表示がされます。

未定義関数の参照ありのdefun

ファイル処理中は未定義関数の参照については情報が出ません。

未定義変数ありのdefun

ファイル処理中は未定義変数の参照については情報が出ません。

未使用変数ありのdefun

; Byte Compiling Top-Level Form: 
; Converted B.
; Compiling DEFUN B: 

; 
; 
; File: cmsg.lsp

; In: DEFUN B

;   (DEFUN B (N))
; Note: Variable N defined but never used.
; 

コンパイル単位の終了

; In: DEFUN C

;   (DEFUN C () N)
; ==>
;   (BLOCK C N)
; Warning: Undefined variable N
; 

; In: DEFUN D

;   (Z)
; Warning: Undefined function Z 
; ; 

; Warning: This variable is undefined:
;   N
; ; 

; Warning: This function is undefined:
;   Z
; 

; Compilation unit finished.
;   4 warnings
;   1 note

; cmsg.sse2f written. ; Compilation finished in 0:00:00.

SBCLと同じく未定義変数/関数の警告がでます。

XCL

次にXCL。ABCLと同じく、SBCLの影響が強いのでSBCLに似ています。
未定義変数は警告のみでスペシャルにはしないようです。

普通の問題ない、defvar、defun

; (DEFVAR *A* ...)
; (DEFUN A ...)

未定義関数の参照ありのdefun

ファイル処理中は未定義関数の参照については情報が出ません。

未定義変数ありのdefun

; (DEFUN C ...)

; in cmsg.lsp
; in (DEFUN C ...)
;
;   Caught WARNING:
;     Undefined variable: N

未使用変数ありのdefun

; (DEFUN B ...)

; in cmsg.lsp
; in (DEFUN B ...)
;
;   Caught STYLE-WARNING:
;     The variable N is defined but never used.

コンパイル単位の終了

; Compilation unit finished
;   Caught 1 WARNING condition
;   Caught 1 STYLE-WARNING condition
;   The following functions were used but not defined:
;     Z (in D)

; Wrote cmsg.xcl (0.0453509986 seconds)

最後に未定義関数の警告あり。

ECL

次にECL。未定義変数は、グローバル変数とされます。
SBCL風にトップレベルフォームの表示あり。 未定義/未使用変数名の表示の前に、!が付くのでgrepしやすいかもしれません。
そして、未定義関数の警告は出ないようです。

普通の問題ない、defvar、defun

;;; Compiling (DEFUN A ...).

未定義関数の参照ありのdefun

ファイル処理中は未定義関数の参照については情報が出ません。

未定義変数ありのdefun

;;; Compiling (DEFUN C ...).
;;; Style warning:
;;;   in file cmsg.lsp, position 2044
;;;   at (DEFUN C ...)
;;;   ! Variable N was undefined. Compiler assumes it is a global.

未定義変数はグローバル変数扱いになります。

未使用変数ありのdefun

;;; Compiling (DEFUN B ...).
;;; Style warning:
;;;   in file cmsg.lsp, position 2002
;;;   at (DEFUN B ...)
;;;   ! The variable N is not used.

コンパイル単位の終了

;;; End of Pass 1.
;;; Emitting code for EVAL-ALWAYS.
;;; Emitting code for OUTFILE.
;;; Emitting code for MSG.
;;; Emitting code for A.
;;; Emitting code for D.
;;; Emitting code for DD.
;;; Emitting code for B.
;;; Emitting code for C.
;;; Emitting code for ZZ.
;;; Emitting code for #:G13.
;;; Finished compiling cmsg.lsp.
;;;

最後にファイルの処理状況が表示されます。

GCL

ECLの元になったCGLですが、ECLと似ています。
未定義変数はグローバル変数とみなされ、未定義関数の警告はなし。
ECLの挙動はGCLに由来しているようですね。

普通の問題ない、defvar、defun

表示なし。

未定義関数の参照ありのdefun

表示なし。

未定義変数ありのdefun

; (DEFUN C ...) is being compiled.
;; The variable N is undefined.
;; The compiler will assume this variable is a global.

グローバル変数にするよという表示。

未使用変数ありのdefun

; (DEFUN B ...) is being compiled.
;; Warning: The variable N is not used.

コンパイル単位の終了

End of Pass 1.  
End of Pass 2.  
OPTIMIZE levels: Safety=0 (No runtime error checking), Space=0, Speed=3
Finished compiling cmsg.lsp.

処理状況を表示して終わり。

Lucid CL

ここで自分の趣味ですが、レトロな処理系も眺めてみます。
未定義変数はスペシャル変数扱い、未定義関数はコンパイル単位終了後で警告です。

普通の問題ない、defvar、defun

表示なし。

未定義関数の参照ありのdefun

ファイル処理中は未定義関数の参照については情報が出ません。

未定義変数ありのdefun

;;; While compiling C
;;; Warning: Free variable N assumed to be special

未使用変数ありのdefun

;;; While compiling B
;;; Warning: Variable N is bound but not referenced

コンパイル単位の終了

;;; Writing binary file "cmsg.lbin"
;;; While compiling top level forms
;;; Warning: The following function is not known to be defined:
;;;          Z was referenced by D

未定義関数の警告あり。

Xerox CL

次にXerox CL。未定義変数はスペシャル扱いで、コンパイル単位終了後、未定義関数の警告です。
また、未使用変数については警告はないようです。

普通の問題ない、defvar、defun

Compiling 2 top-level forms ... Done
Compiling DEFVAR *A* ... Done

Compiling 2 top-level forms ... Done Compiling DEFUN A ... Done

N top-level formsと出ますが、これはマクロ展開後のフォーム数でしょうか。若干の謎です。

未定義関数の参照ありのdefun

ファイル処理中は未定義関数の参照については情報が出ません。

未定義変数ありのdefun

Compiling DEFUN C 
Warning: The variable N was unknown and has been declared SPECIAL.
... Done

未使用変数ありのdefun

表示なし。

コンパイル単位の終了

Compiling 4 top-level forms.
Warning: The following functions were called in the code just compiled, but are not known to exist:
  Z -- called from D.

長めの警告文で、未定義関数を警告。

VAX LISP

次にVAX LISPですが、未定義変数はスペシャル扱い、未定義関数は終了時警告です。
未定義変数については終了時にも表示されます。

普通の問題ない、defvar、defun

A compiled.

defun等はありますが、defvarの表示はなしのようです。

未定義関数の参照ありのdefun

ファイル処理中は未定義関数の参照については情報が出ません。

未定義変数ありのdefun

Warning in C
  N not declared special or bound lexically, assuming special.
  Surrounding forms:
    N
    (BLOCK C N)
C compiled.

未使用変数ありのdefun

Warning in B
  N bound but value not used.
B compiled.

コンパイル単位の終了

Finished compilation of file SYS$SYSDEVICE:[USER.MASSO]CMSG.LSP;3
0 Errors, 3 Warnings
The following are assumed to be functions, but were not
declared or defined:
 Z 
The following are assumed to be special variables, but were
referenced free without being declared or defined:
 N 

最後にコンパイル単位での未定義関数/変数の警告が要約された形で出ます。
なかなか良いですね。

Symbolics CL

次に、Symbolics Open Genera の処理系ですが、

普通の問題ない、defvar、defun

表示なし(who lineというところにコンパイル中の関数はちらっと表示される)

未定義関数の参照ありのdefun

ファイル処理中は未定義関数の参照については情報が出ません。

未定義変数ありのdefun

For Function C
While compiling N:
The variable N is unknown and has been assumed SPECIAL

未使用変数ありのdefun

For Function B
The value of variable N was never used.

コンパイル単位の終了

The following functions were referenced but don't seem defined:
 Z referenced by D

最後に未定義関数の警告が出ます。

まとめ

以上、だらだらと列記しましたが、VAX LISPあたりがシンプルかつ丁寧という感じですね。
SBCLは分量が多くて親切ともいえますが、逆に欲しい情報が埋もれてしまっている感もあります。


HTML generated by 3bmd in SBCL 1.2.10

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)

Older entries (1919 remaining)