#:g1: frontpage

 

勝手に「日本LispWorksユーザー会(非公式)」を作りました

Posted 2018-05-26 17:39:01 GMT

私の身近にLispWorksを使う人がちらほら増えてきているのですが、バグやパッチの情報だったり、便利な使い方だったりの情報共有をもっと活発にしたいなと思い、Google Groupsで勝手に「日本LispWorksユーザー会(非公式)」というのを作成しました。

主な用途・ターゲットユーザー

  1. LispWorksのパッチやバグの情報共有
  2. LispWorks全般、特に日本語処理周りについての情報共有

が主な目的で、その辺りに興味がある方々がターゲットユーザーです

まあ、Lisp Hugメーリングリストで尋ねてみたら?というアドバイスが殆どになってしまうかもしれませんが、それはそれで良いかなと思っています😃


HTML generated by 3bmd in LispWorks 7.0.0

an obscure feature of MacLISP

Posted 2018-05-13 13:53:03 GMT

小学館ランダムハウス英和大辞典のオンライン版で、“obscure”の用例に何故かMACLISPが出てきているのを発見しました。

goo 英和・和英辞書 「an obscure feature of MacLISP」の意味

an obscure feature of MacLISP

説明書に書かれていない MacLISP (コンピュータ言語)の一特徴.

ちゃんとMITっぽい言い回しですが、一体どこから紛れ込んだのでしょう……。


HTML generated by 3bmd in LispWorks 7.0.0

Lispに有理数(分数)が導入されたのはいつ頃なのか

Posted 2018-05-09 15:03:26 GMT

turingcomplete.fm #17 を聴いていたら、Lispと有理数について、またそれはいつ頃導入されたのかという話題がでてきました。
そういえばLispが有理数をサポートしたのっていつ頃なのか私も知らないな、ということでちょっと調べてみましたが、1981年あたりが境ではないかと推測できたものの、結局あまりはっきりしたことは分からず。
とはいえ、折角調べたのでまとめて記事にしてみることにしました。

Common Lisp

しかし、とりあえず「Lispは数値計算周りの仕様が充実している」という評判はCommon Lisp(1984)辺りが始まりのようです。
次にSchemeが、RRRS(1985)でCommon Lisp(1984)の成果を評価し、それを取り入れたようですが、その際にCommon Lispでは定義されていない正確数と非正確数の概念ができたようです。
当時の背景があまり想像できませんが、移植性のある正確数とそうでないものに分けたのでしょうか。
RRRSではCommon Lispより前には体系的に数値計算をまとめたLispはなかったと述べられているので、Common Lispが一つの境なのでしょう。

S-1 Lisp

では、そのCommon Lispは、どこから有理数を取り入れたかというと、当時の科学計算用スーパーコンピュータ用LispであるS-1 Lisp(S-1 NIL)からのようで、取り入れる方針が決まったのは1981年位のことのようです。

  • The Evolution of Lisp:

    Fancy floating point numbers, including complex and rational numbers (this was the primary influence of S-1 Lisp)

S-1 LispはS-1 NILとも呼ばれるように、方言としてはNILであり、Common Lispが制定された頃にはNILはCommon Lispになってしまっているので、どこからNILでどこからCommon Lispかは分かりませんが、数値計算回りでのS-1 Lispの影響は、かなり大きそうです。
S-1 Lispでは、数値計算回りでハードウェアの支援が手厚く、有理数のサポートでもハードウェアの支援を受けることができていたようです。

Lisp Machine Lisp

また、同時期の1981年後半には、S-1 Lispとは独立してLisp Machine Lisp(Zetalisp)(CADR System 74)にも導入されていますが、製品として世の中に出たLispとしては最初のものではないでしょうか(Symbolics/LMIから製品化)。
ちなみに、Lisp Machine Lispの方の有理数は、当初は、エスケープ文字が/なので、3\10のように記述されていましたが後のCommon Lisp化で3/10のように表記されるようになったようです。
また、Lisp Machine Lispでは、Common Lispのように整数との統合はされていないようで1\11は別物になるようです。

1981年のLispコミュニティ

Lispコミュニティでは、1981年辺りで有理数の議論がよくされていたようで、1981年あたりに何らかの機運があったのかなと思います。

Lisp、Smalltalkの双方で活躍していたL Peter Deutsch氏がSmalltalk-80には有理数サポートがあるという投稿をしていますが、こういう発言があるということはやはり1981年辺りで有理数をサポートしたメジャーなLisp処理系はなかったのではないかと思います。


HTML generated by 3bmd in LispWorks 7.0.0

いつのまにやらArc公開十周年

Posted 2018-04-15 19:31:36 GMT

すっかり忘れてしまっていましたが、「百年の言語」を体現するarcが公開されたのは、十年前の2008-01-29のことでした。
本当は、今年の01-29に何か書こうと思っていましたが、それさえ忘れて早二ヶ月半……。

arcのソースコードが公開されたのは2008年でしたが、arcで構築されたサイトのHacker Newsは、2007年から稼動しています。
また、名前と構想が発表されたのは、2001年のことなので、かれこれ十八年ともいえなくもないです。

最近のarc

arcの近況を眺めてみましたが、オフィシャルなリリースは、2009-08の3.1から9年間動きなしです。
Hacker Newsを動かしていたのはarcでしたが、2014年にpg氏がY Combinatorの日常業務から引退し、今ではまだarcで動いているのかは良く分からないようです。

軒並動きはないのかと思いましたが、arcのコミュニティが開発しているAnarkiの方は最近も更新があるようです。
Racketに#lang anarkiを作成するなど、なかなか面白そう。

早速、試してみましたが、Racketがインストールされた環境であれば、

raco pkg install anarki

とするだけで導入されます。

あとは、こんな感じで記述しRacketから使えます。 (無理にarcっぽさを出してみました。)

#lang anarki

;;; fib.rkt

(:provide fib)

(def fib (n) (if (< n 2) n (+ (fib:- n 2) (fib:- n 1))))

;; Racket
(require "fib.rtk")

(fib 40) ;→ 102334155

むすび

もっと色々書こうと思いましたが、昔の文献を読んでいたらお腹一杯になって満足しました。

参考

このブログのarc関係の記事


HTML generated by 3bmd in LispWorks 7.0.0

defvar で値を再設定したい時

Posted 2018-04-10 22:09:49 GMT

defvarには値が設定されている場合には再設定をしないという利点がありますが、開発中などはdefvarの値を再設定したい場合がままあります。

こんな時の為に便利マクロを定義するという記事を読みました。

目立たない機能なのであまり知られていないですが、Emacs系のLispエディタ上ではdefvarのフォームを評価したら値が再設定される、という機能があります。
どうやらdefvarが誕生した(1979年頃)からある機能の様子。
(多分、defvar誕生の頃から扱いがめんどくさいという認識があったのでしょう……)

Zmacs(ZWEI)だと、COM-EVALUATE-REGION-HACKというのがあって、defvarsetqに置き換えて評価するようです。

slimeだとslime-eval-defunで評価すれば、defvarを認識して下請けのslime-re-evaluate-defvarで値を再設定します。
再設定の方法は、一度makunboundしています。

LispWorksだと、“Evaluate Defun”が“Reevaluate Defvar”を呼んで再設定します。slimeと同じくmakunboundするようです。
エディタ変数editor::evaluate-defvar-actionの値が、:reevaluate-and-warn:reevaluateの場合だけこの動作になるので好みで入切可能です(デフォルトは有効)

Hemlockだと、“Re-evaluate Defvar” コマンドのみで、“Evaluate Defun”とは連動しないようです。こちらもmakunboundして再設定。

新しい値でsetqするものかと思っていましたが、案外makunboundで処理する方式の方が多いみたいですね。

むすび

“Reevaluate Defvar”は、リージョンを評価した時などにも発動していることがあります。
大抵の開発時は、再評価してもらった方が嬉しいですが、defvarの値設定まわりがどうも不思議な挙動だなと思ったら、確認してみるのも良いかと思います。

関連


HTML generated by 3bmd in LispWorks 7.0.0

CONSマシン時代のLisp Machine Lispのマニュアル

Posted 2018-04-04 19:46:48 GMT

MIT Lispマシンのソースディレクトリを漁っていたら、MITの最初のLispマシンであるCONS時代のLisp Machine Lispのマニュアルらしきものをみつけたので、html化してみました。

日付は、1977-06-08ですが、CONS時代のドキュメントであるAIM-444: LISP Machine Progress Reportと照らし合わせてもCONS時代で間違いないようです。

AIM-444の最後に書いてあるように、プロトタイプであるCONSは、1977年に一応の完成となり、1978年あたりから実用を目的としたCADRの開発が始まります。

CONS時代のLisp Machine Lispで興味深い所

EVERYSOMEはINTERLISPから輸入してきたものであることが書かれていたりします。

また、CONS時代にはまだ、パッケージがないのですが、どうやら接頭辞で管理していたようです。
雰囲気としては今のEmacs Lispに近いですが、これらは、PREFIX REGISTRYのまとまりとして何らかのお作法があった様子。

現在のCommon Lispにも存在するread-from-stringが既にありますが、read-は接頭辞だったようなので、当初は、Common Lispでいうとread:from-stringみたいな感じだったのかもしれません。
しかし、これらの接頭辞は、その後パッケージができても名前はそのまま変更されることもなく後の慣習と混ざり、なんとなく命名規約が二重になった感じを残したようです。

また、現在のLispでは単語を-で繋ぎますが、一部_で繋いだものも存在したようです。これらは後に-で統一されます。

%は現在の命名慣習でも公でない内部関数を表わしたりしますが、初期のLisp Machine Lispでもそのようです。
この後、CADR時代になってくると、マイクロコードで書かれた低レイヤーのサブプリミティブで値を返すことが前提になっていないものに%が付くという慣習になって行ったようです。

%が重なる%%の命名規約は謎です。安直に考えると、さらに内部の関数という感じですが、どうもそうでもない雰囲気があります。

面白いのが、下請けを意味する*と併用されたりすることで、例えば、%*NCONCは、可変長のNCONCの二引数版でかつ公でない関数、という感じになります。また、%*GENSYM-COUNTERのように変数に適用されることもあるようです。

TRACE-
GRIND-
TV-
PC-PPR-
KBD-
FILE-
PRINT-
READ-
FASL-
ED-
ED-COM-
%
%%
*
%*
DTP-
%%Q-
CDR-
%%AREA-MODE-
FSM-
%SYS-COM-
ADI-
%%ADI-
LP-
%%LP-
%%ARG-DESC-
%FEF-
%%FEF-
FEF-
%%ARRAY-
%ARRAY-
ARRAY-
ART-
FEFH-
%FEFH-
%%FEFH-
FEFHI-
%%FEFHI-
%%PHT1-
%%PHT2-
%PHT-
SG-
%%SG-
MESA-
%%MESA-

むすび

CONS時代のLispがどのようなものだったかの資料は少なく、AIM-444: LISP Machine Progress Reportに書いてあることも謎が多かったのですが、このマニュアルで若干謎が解明されました。
また、Lisp Machine LispはCADRから始まるような印象がありましたが、初期のものを含めると1975、6年あたりから存在するといえそうです。


HTML generated by 3bmd in LispWorks 7.0.0

マストドンにreddit r/lisp r/lisp_jaの新着ボットをつくりました

Posted 2018-04-02 16:51:52 GMT

Lispな人でマストドンを利用している人がどれだけいるのかは分からないのですが、ボットを作成するのが簡単だったので、Twitterボットが投稿するついでにマストドンにも投稿するようにしてみました。

r/lisp_jaと、その他英語Lisp系サブレディットの詰め合わせ版の二種があります。

TLのお供にどうぞ。

ちなみに、Twitterはこちらです。


HTML generated by 3bmd in LispWorks 7.0.0

Common LispのLOOPやFORMATを真似した例は割とある

Posted 2018-03-28 07:45:44 GMT

実際の所、LOOPやFORMATはCommon Lispを使ったことがない人達にも複雑さの象徴のように語られていますが、割合にCommon Lisp以外にも輸出されています。
どちらもライブラリレベルの機能なので導入も難しくはありません。

言語仕様に入っているかどうか、ということならば、いまどきはライブラリレベルで実現できるものは言語のコアに入れず、標準ライブラリ位にまとめる所をCommon Lispでは全部標準仕様内にまとまった構成になっている、という点が特徴ですが、言語設計でそういう選択をしたので、こうなっています。
また、LOOPやFORMATはCommon Lisp以前から存在しますが、Common Lispより前のLispではライブラリとして導入されています。

コアに入っているかどうかということになると、LOOPはPython等の内包表記と機能的にはほぼ変わらないもので、ある意味先祖的なものに感じます。
内包表記の難解さに対する批評がありますがLOOPへの批評も似たものがあります(前置 vs 中置の議論の他に)

FORMATも繰り返しがあったりする所が珍しいですが、繰り返し以外では、いまどきの言語に似たようなものは多数ありますし、より多機能なものも多いです。

ちなみに、LOOPやFORMATよりいまどきの言語が標準搭載している正規表現エンジンの方が複雑だと思いますが、正規表現エンジンはCommon Lispでは標準規格にはないので、みなライブラリを利用しています。

ライブラリレベルで移植されたLOOPやFORMAT

ライブラリレベルで移植された例をざっと挙げてみましょう。

LOOP

INTERLISPのclispという環境があり、そこで中置的に書ける構文のforがMACLISP系Lispに取り入れられました。 主にMIT Lispマシングループで良く使われ、今に至ります。

Common LispのLOOPでできることは殆どINTERLISPのforで可能なのでLOOP自体が「誰かが真似したもの」でした

Common Lisp以外に移植された例

Scheme

Emacs Lisp

Clojure

等々、多数あります。

全部乗せな繰り返し構文というLOOPと似たコンセプトのものだとsrfi-42を筆頭に多数あります。

また、Common Lisp内でもLOOP代替構文というのは多数あります。

FORMAT

FORMATの出自

FORTRANのFORMATをMACLISPに導入したのが始まりのようです。

Clojure

Scheme

Emacs Lisp

全部乗せな出力構文というFORMATと似たコンセプトのものだとfmtを筆頭に結構あります。

むすび

実際の所、FORMATやLOOPは大したものでもなく、フルスペックでも大体1000行前後の実装が多いので、ひまつぶしのログラミングの題材には丁度良いと思います。


HTML generated by 3bmd in LispWorks 7.0.0

「Generatorの勧め」をCommon Lispで

Posted 2018-03-25 09:11:27 GMT

ClojureにTransducers、SchemeにGenerator(SRFI-158/SRFI-121)だそうですが、Common Lispだったらseriesでしょう、ということでCommon Lisp版を書いてみました。

target1が「リスト生成→フィルタリング」、target2が「ジェネレータを使ってストリーム的に処理」するので中間コンスが少なくできる、という内容です。

今回の場合は、そっくりそのままCommon Lisp+seriesで写しとれる感じです。

下準備

;;; scheme
(define size 1000)

;;; cl
(ql:quickload :series)

(defpackage gen (:use :cl :series))

(in-package :gen)

(defconstant size 1000)

target1

;;; scheme
(define (target1)
  (filter (lambda (x) (zero? (mod x 3)))
          (map cdr
               (filter (lambda (x) (odd? (car x)))
                       (map (lambda (x) (cons x (square x)))
                            (iota size))))))

;;; cl
(defun target1 ()
  (remove-if-not (lambda (x) (zerop (mod x 3)))
                 (mapcar #'cdr
                         (remove-if-not (lambda (x) (oddp (car x)))
                                        (mapcar (lambda (x) (cons x (expt x 2)))
                                                (loop :for i :from 0 :repeat size :collect i))))))

target2

(define (target2)
  (generator->list
   (gfilter (lambda (x) (zero? (mod x 3)))
            (gmap cdr
                  (gfilter (lambda (x) (odd? (car x)))
                           (gmap (lambda (x) (cons x (square x)))
                                 (make-iota-generator size)))))))

(defun target2 ()
  (collect 
   (choose-if (lambda (x) (zerop (mod x 3)))
              (map-fn t
                      #'cdr
                      (choose-if (lambda (x) (oddp (car x)))
                                 (map-fn t
                                         (lambda (x) (cons x (expt x 2)))
                                         (scan-range :length size)))))))

target2seriesのリーダーマクロを使えばこんな感じにも書けます。
seriesを使うならこっちが普通かもしれません。

(series::install)

(defun target2/ ()
  (collect 
   (choose-if (lambda (x) (zerop (mod x 3)))
              (#Mcdr (choose-if (lambda (x) (oddp (car x)))
                                (#M(lambda (x) (cons x (expt x 2)))
                                   (scan-range :length size)))))))

implicit-mapを有効にすれば、更に簡潔に書けますが、ソースの字面で若干混乱しそうになるのでお勧めはしません :)

(series::install :implicit-map T)

(defun target2// ()
  (collect 
   (choose-if (lambda (x) (zerop (mod x 3)))
              (cdr (choose-if (lambda (x) (oddp (car x)))
                              ((lambda (x) (cons x (expt x 2)))
                               (scan-range :length size)))))))

target3

ついでにdo職人による極力コンスを排したコードも参加してみます。

(defun target3 ()
  (do* ((x 0 (1+ x))
        (y (expt x 2) (expt x 2))
        (ans (list nil))
        (tem ans))
       ((= size x)
        (cdr ans))
    (when (and (oddp x) (zerop (mod y 3)))
      (setf (cdr tem)
            (setq tem (list y))))))

比較結果

今回はタイムというより無駄なコンスを減らすことができるかがポイントのようです。
なお回数は一万回に増やしてみました。

(defparameter *count* 10000)

(equal (target1) (target2)) → T

(time (dotimes (i *count*) (target1))) #|| Timing the evaluation of (dotimes (i *count*) (target1))

User time = 0.521 System time = 0.000 Elapsed time = 0.508 Allocation = 678292704 bytes 0 Page faults Calls to %EVAL 160036 ||#

(time (dotimes (i *count*) (target2)))

#|| Timing the evaluation of (dotimes (i *count*) (target2))

User time = 0.236 System time = 0.000 Elapsed time = 0.226 Allocation = 198294392 bytes 0 Page faults Calls to %EVAL 160036 ||#

seriesを使ったtarget2方がコンスはtarget1の約29%に縮減し、タイムも倍速くなりました。
(ちなみに、srfi-158では67%の縮減のようです。)

vs do 職人コード

do 職人のtarget3とも比較してみます。

target2と速度はあまり変わりませんが、コンスはさらに縮減できてtarget1の約5%になりました。

(equal (target1) (target3))
→ T

(time (dotimes (i *count*) (target3)))

#|| Timing the evaluation of (dotimes (i *count*) (target3))

User time = 0.213 System time = 0.000 Elapsed time = 0.203 Allocation = 38295784 bytes 0 Page faults Calls to %EVAL 160036 ||#

といっても、途中でnn^2のペア作ってないし卑怯!となってしまうと思うので、中間でペアを作りつつdo職人の結果を目指します。

seriesはストリーム的な書法の他に外部イテレータ的な書き方も可能で、下記のように書いてみました。

なぜこう書くかというと、ペアを作ってペアをばらすのを同一のスコープに収めてdynamic-extent指定し、コンスは無かったことにしたいからで、標準APIのストリーム的な書法ではちょっと難しいです。
(多分ストリームを二本作れば可能)
なおdynamic-extent指定をしなくても多値を使って同様の効果が得られます(がペアを作る縛りなので……)

計測してみると、do職人コードと遜色なくなりました。

(defun target4 ()
  (let ((g (gatherer #'collect)))
    (iterate ((x (scan-range :length size)))
      (let ((x (cons x (expt x 2))))
        (declare (dynamic-extent x))
        (when (oddp (car x))
          (let ((x (cdr x)))
            (when (zerop (mod x 3))
              (next-out g x))))))
    (result-of g)))

(equal (target1) (target4))
→ T

(time (dotimes (i *count*) (target4)))

#||| Timing the evaluation of (dotimes (i *count*) (target4))

User time = 0.232 System time = 0.000 Elapsed time = 0.220 Allocation = 38939848 bytes 0 Page faults Calls to %EVAL 160036 ||#

ちなみに、dynamic-extent指定を削除するとコンスはtarget1並みに増えるようです。

#||
Timing the evaluation of (dotimes (i *count*) (target4))

User time = 0.244 System time = 0.000 Elapsed time = 0.231 Allocation = 198940456 bytes 0 Page faults Calls to %EVAL 160036 ||#

まとめ

遅延リスト、ストリーム、ジェネレータ辺りで何かしようと思ったらseriesも結構使えることがあります。

ただseriesは色々特殊なので、もうちょっと整理された次世代seriesがあったら良いなあという近頃です。


HTML generated by 3bmd in LispWorks 7.0.0

どう書く?org: ローカル変数の一覧を取得 (Common Lisp)

Posted 2018-03-18 13:46:33 GMT

久々にonjoさんのページを眺めていて、どう書く?orgの問題を解いているページに遭遇しました。

かれこれ10年以上前のことですが、そういえばこんな問題あったなあと懐しくなりました。
下記のような問題です。

どう書く?org: ローカル変数の一覧を取得

リフレクション系のお題の続編です。 ローカル変数の内容を取得して連想配列(ハッシュ、辞書など)に詰める コードを書いてください。

Pythonで表現すると、下のコードの???部分を埋めることになります。

>>> def foo(): x = 1 y = "hello" ??? return result

>>> foo() {'y': 'hello', 'x': 1}

なお、どう書く?orgは残念ながらアダルトサイトと化してしまったようなのでリンクはarchive.orgです。

Lisp方言の他の方々の回答は、基本構文に仕込みを入れて、Pythonでいうlocalsみたいなものをサポート、という感じです。
自分は、この問題は解いたことがなかったので、色々考えてみました。

Common Lispにおいてローカル変数とは

お題はPythonが念頭にあるようですが、Lisp-1なPythonならローカルのスコープに漂っているのは変数位のものです。
しかし、Lisp-2の権化たるCommon Lispだと、関数、マクロ、シンボルマクロ、スペシャル変数等々がローカルスコープに存在しうります。

実際的にコードウォーキングして何かをする場合、ブロック、GOタグ等を含めていじること多いので、変数だけ取得できても、そんなに嬉しくない気はしますが、それはさておき解答を考えてみます。

解答その一 (実際的に考える)

実際の所、実行時にスコープ内のローカル変数名の一覧を取得したいことというのは殆どありません。
また、遅くなっても良いのだったら、別に言語のスコープのコンテクストをいじらなくても、連想配列等でエミュレートできそうです。

とりあえず、Common Lispには、実行時に名前で変数をアクセスする仕組みとしてスペシャル変数があるので、そういう時はスペシャル変数を使うのが良いだろうということで、こんな感じに書いてみました。

(defun locals () nil)

(defmacro progx ((&rest binds) &body body) `(progv ',(mapcar #'car binds) (list ,@(mapcar #'cadr binds)) (flet ((locals () (append (list ,@(mapcar (lambda (b) `(cons (quote ,(car b)) ,(cadr b))) binds)) (locals)))) ,@body)))

(defun foo (&aux result)
  (progx ((outer 42))
    (progx ((x 1)
            (y "hello"))
      (setq result (locals))
      result)))

(foo)((x . 1) (y . "hello") (outer . 42))

上記のprogxは、実行時の変数結合を実現するprogvをラップしたもので、ボディ内部のlocalsの実行で指定された変数名が取得できます。

スペシャル変数のアクセスはそこまで遅くないので、もし実際的に必要なことがあれば、こういうものでまかなえるのではないでしょうか。

その二 (定義時に処理する)

onjoさんの解答がこちらの系統ですが、onjoさんはAllegro CLのインタプリタ動作時のみ対応とのことでした。
しかし、実行時に外から飛び込んでくるレキシカル変数名というのは考慮しなくても良さそうなのと、外から飛び込んでくるスペシャル変数名は、上記progxのようなもので別途対応すれば良いだろうということで、定義時(マクロ展開時)に確定した情報を取得できれば良しという方針でシンプルに書いてみました。

#+lispworks
(defun get-local-variables (env)
  (mapcar #'compiler::venv-name (compiler::environment-venv env)))

#+sbcl (defun get-local-variables (env) (mapcar #'car (sb-c::lexenv-vars env)))

#+allegro (defun get-local-variables (env) (let* ((base (sys::augmentable-compilation-environment-base env)) #+(:version= 8) (vartab (sys::augmentable-environment-variable-hashtable base)) #+(:version>= 9) (vartab (sys::ha$h-table-ht (sys::augmentable-environment-variable-hashtable base)))) (loop :for var :being :the :hash-keys :of vartab :when (typep (sys:variable-information var env) '(member :lexical :special)) :collect var)))

(defmacro locals (&environment env) `(list ,@(mapcar (lambda (v) `(list ',v ,v)) (get-local-variables env))))

肝は、ローカル変数の一覧が取れるかどうかなのですが、Clozure CLでは取得の方法が分からず対応していません。
ローカル変数の一覧が取れる処理系であれば同様の方法で取得できるかなと思います。

試してみる

こんな感じになります。

(defun foo (&aux result)
  (let ((outer 42))
    (declare (special outer))
    (flet ((bar ()
             (let ((x 1)
                   (y "hello"))
               (setq result (locals))
               result)))
      (bar))))((y "hello") (x 1) (outer 42) (result nil))

また、変数は宣言してから使うことになるので、Pythonと違ってresultも計上されることになりますし、ローカル変数外側のスコープものも見えます。

ちなみに、Pythonで確認してみたところ、外のブロックのものは取得しない様子。

def foo():
  outer = 42
  def bar():
    x = 1
    y = "hello"
    result = locals()
    return result
  return bar()

{'y': 'hello', 'x': 1}

しかし、一番内側のブロックで変数を使えば見えるようです。
どうも、中途半端なような。

def foo():
  outer = 42
  def bar():
    x = 1
    y = "hello"
    z = outer
    result = locals()
    return result
  return bar()

{'y': 'hello', 'x': 1, 'z': 42, 'outer': 42}

まとめ

どう書くorgのページでも議論されていましたが、処理系がデバッグ情報として変数名を含める際にこういう機能が必要になることが多いと思われますが、ユーザーが触って何か有益な処理をするというのはあまり無さそうです。

なお、ご存知の通り、Common Lispでは対話的なデバッガの機能が充実しているので、ローカルな変数名等は大抵見えたり、変更したりもできます。


HTML generated by 3bmd in LispWorks 7.0.0

RMSはCommon Lispの言語仕様をとりまとめた主要人物ではない

Posted 2018-03-10 07:44:34 GMT

こんにちはCommon Lispポリスメンです。

RMSはとても偉大ですが、Common Lisp仕様の議論には参加してるものの、主要人物ではありませんでした。
ちなみにCommon Lisp仕様の議論には何十人もの人が参加しています。
また、主要人物は4人ではなく、5人でした。

  • The Evolution of Lisp

    The core group eventually became the “authors” of CLtL I: Guy Steele, Scott Fahlman, David Moon, Daniel Weinreb, and Richard Gabriel. This group shouldered the responsibility for producing the language specification document and conducting its review. The self-adopted name for the group was the “Quinquevirate” or, more informally, the “Gang of Five”.

また、ANSI Common Lisp規格策定のX3J13のメンバーでもありません。

おそらくですが、RMSとGLSが混ざってしまったのではないでしょうか(LispとEmacsあたりで)
RMSが議論に参加している様子は、仕様策定メーリングリストで眺めることができますので、ご興味があればどうぞ。

また、Common Lisp(1984)の謝辞にも登場しています。

その他おまけ

RMSが書いたっぽいLispコードの例


HTML generated by 3bmd in LispWorks 7.0.0

Common Lispのeval-whenとコンパイラかインタプリタ実装かは別のレイヤーの話

Posted 2018-03-07 20:02:22 GMT

先日Twitterでこんなやりとりを目にしました

たしかに太古のMACLISPや、Franz Lispでは、変数の結合がダイナミックだったりして、コンパイラ(静的に決まることが多かった)かインタプリタ(基本動的)かで挙動が変わることが悩みのタネだったことがありました。

しかし……Common Lispではコンパイラかインタプリタ実装かで実行に差異はない

eval-whenというからにはLispが指しているのはCommon Lispだと思いますが、Common Lispでは、評価器がコンパイラかインタプリタ実装かで違いはでないこと、と規定されています。
また、プログラムがどちらの方式で実行されているか知る手立てもありません。

evaluation n. a model whereby forms are executed , returning zero or more values.
Such execution might be implemented directly in one step by an interpreter or in two
steps by first compiling the form and then executing the compiled code; this choice is
dependent both on context and the nature of the implementation, but in any case is
not in general detectable by any program. The evaluation model is designed in such a
way that a conforming implementation might legitimately have only a compiler and
no interpreter, or vice versa. See Section 3.1.2 (The Evaluation Model).

Common Lispがするコンパイルと実装戦略は別

じゃあ、Common Lispのcompileとかcompile-fileはどうなるんだ、と思うかもしれないですが、Common Lispが規定するコンパイルプロセスは、評価器の実装戦略とは別のレイヤーの話です。

以下、ややこしいのでコンパイルプロセスの方をコンパイルと書きます。
Minimal Compilationというのが定められていますが、インタプリタのみの処理系でも、コンパイルしてロードして、というのは可能で、その場合にはコンパイルによってマクロ展開等の前処理的なものを省いたものを解釈していくことになると思われます(多分)

また逆に、evalが即ちインタプリタということもなく、フォームの一塊のツリーをコンパイルしてから処理しても問題ありません。

Minimal compilation is defined as follows:
• All compiler macro calls appearing in the source code being compiled are expanded, if at
all, at compile time; they will not be expanded at run time.
• All macro and symbol macro calls appearing in the source code being compiled are
expanded at compile time in such a way that they will not be expanded again at run
time. macrolet and symbol-macrolet are effectively replaced by forms corresponding to
their bodies in which calls to macros are replaced by their expansions.
• The first argument in a load-time-value form in source code processed by compile
is evaluated at compile time; in source code processed by compile-file , the compiler
arranges for it to be evaluated at load time. In either case, the result of the evaluation is
remembered and used later as the value of the load-time-value form at execution time.

eval-whenとは

eval-whenは、主に処理が複数に分かれてしまう(ファイルをコンパイルしてロードする等)場合に生じる問題を解消するためのもので、コンパイル中にコンパイル中に定義した関数を使いたいとか、コンパイル済みのファイルをロードする場合に特定の仕事をさせたい等々のことに利用します。

Emacs Lispのeval-when-compileも似たようなものだと思いますが、脚注のコメントによると、Common Lispの#.(リード時評価)により近いそうです。

用途としてはCommon Lispと同じではないでしょうか。インタプリタ実装でもコンパイルは可能なので、Emacs Lispは、100%インタプリタ実装のCommon Lispに近い感じなのかもしれません。

他の方々からの指摘

上記で説明したように、挙動は変わないことになっています。

上記で説明したように、eval-whenと実装戦略は別のレイヤーで、これらの組み合わせがコードの意味を変えることはありません。

まとめ

Common Lispではコンパイラかインタプリタ実装かで実行に差異はない、と書きましたが、これは過去のLispでの問題を解決する歴史の上に成り立ったものでした。
Common Lispの歴史の中でも、ANSI規格以前のCLtL1では、*applyhook**evalhook*compiler-let等、コンパイラ/インタプリタ動作で整合性が取れなくなる/取るのが難しい機能が散見されましたが、これらはANSI規格として煮詰められる際に廃止となっていたりします。

また、現代のCSの意味論からすると不思議なこともあると思いますが、CSの意味論が洗練/確立する前からLispのコンパイラ/インタプリタは存在します。
長い歴史の中で、互いに影響しあったかもしれませんし、同じような結果になったとしても道程は全然違うものかもしれません。

コンパイラ……インタプリタ……コンパイラがコンパイル……と書いてる間に段々良く分からなくなってきたので、間違いがあったらご指摘よろしくお願いします。

ちなみに何故Twitterの議論には参加していないのかというと、自分は鍵アカウントの為で、ブログで失礼しました。


HTML generated by 3bmd in LispWorks 7.0.0

マクロ展開コードの副作用から起きる問題の特別に汚い例が気になる

Posted 2018-01-21 09:01:25 GMT

On Lispの10.3 マクロ展開関数の副作用には、

MillerとBensonのLisp Style and Designは 展開コードの副作用から起きる問題の特別に汚い例に言及している. Common Lispでは...

とあります。
恐らく、「特別に汚い例に言及している.」の後は、Lisp Style and Designが言及している内容を具体的に紹介しているのだとは思いますが、いまいち文の繋がりが良くなく思え、言及しているという紹介のみで、以降は全く別の話という可能性も無くはないと思えてきてしまったので、実際にLisp Style and Designでは何が書かれていたのか確認してみました。

ネタ元

Lisp Style and Designでは、マクロの展開時の問題について解説していますが、恐らく、4.2.5.2 Problems to Avoid When Writing Macros(P.85)のようです。

この段落では、主にマクロ展開時に副作用を使うと、どういう悪い結果が齎されるかについて、具体例を挙げて解説していますが、締めに&rest&bodyのリストの破壊的変更が解説されています。

ということで、On Lisp 10.3章の「言及している」から章末までは、Lisp Style and Designでの解説を元ネタにしてます、ということだったようです。

ちなみに、On Lisp 10.3章では、副作用の例として、マクロ展開時に変数を更新することについてを取り上げていますが、Lisp Style and Designでは、変数のspecial宣言を取り上げていて、On Lispでinternの問題が追加になった以外は、両書ともほぼ同じ問題を紹介していたようです。


HTML generated by 3bmd in LispWorks 7.0.0

マクロに付くコンパイラマクロの使い道

Posted 2018-01-15 18:11:35 GMT

コンパイラマクロは関数だけでなくマクロにも定義できます。
HyperSpecのdefine-compiler-macroの項目にも明記されているのですが、一体どういう所で使うのかと思っていました。

コンパイラマクロの使い方としては、基本的にセマンティクスが変わらないことが絶対条件で、あとは何らかの効率が良くなるようなコード変換をすることになるんだと思います。
マクロでこういうパターンはどういう場合かを考えてみましたが、TAOのforみたいな場合に効率的な処理に展開できそうなので、ちょっと試してみました。

なお、マクロにコンパイラマクロを定義して、マクロなのにfuncallが効くように見せ掛けるテクニックを目にしたことがありますが、これはセマンティクスが変わってしまうのでNGかなと思っています。

TAOのfor

TAOのマニュアルによると、forは、

  形式 : for var list form1 form2  ...
form1 form2 ... を var を使って順に実行する。 var は list の各要素に
逐次束縛されたものである。 form1 form2 ... は list の長さと同じ回数評価
される。 nil を返す。

とあって殆どdolistと同じ挙動なのですが、indexというSRFI-1のiotaのような関数と組み合せて使う場合、実際には数値リストが生成されないという説明があります。

(index 0 10)(0 1 2 3 4 5 6 7 8 9 10) 

(let ((ans nil)) (for i (index 0 10) (push i ans)) ans)(10 9 8 7 6 5 4 3 2 1 0)

恐らく実際のTAOもマクロ展開のパターンマッチで展開を変えているんだと思いますが、こういうケースのマクロ展開にコンパイラマクロが使えそうです。

とりあえず、ベースの関数とマクロを定義します。

(defun index (start end &optional (step 1))
  (if (plusp step)
      (loop :for i :from start :upto end :by step :collect i)
      (loop :for i :from start :downto end :by (- step) :collect i)))

(defmacro for (var list &body body) `(dolist (,var ,list nil) ,@body))

コンパイラマクロで中間リストの生成を無くす

そしてコンパイラマクロを定義します。

下記では、無駄にメソッドを使っていますが、こういう場合には嵌るかなと思って試してみました。
メソッドディスパッチでindexだけでなく、iotaにも対応してみています。

(define-compiler-macro for (&whole whole var list &body body)
  (for-cm-expander (car list) var list body whole))

(defgeneric for-cm-expander (fn var list body whole) (:method (fn var list body whole) (declare (ignore fn var list body)) whole))

(defmethod for-cm-expander ((fn (eql 'index)) var list body whole) (declare (ignore whole)) (destructuring-bind (index start end &optional (step 1 stepp)) list (declare (ignore index)) (if stepp `(if (plusp ,step) (loop :for ,var :from ,start :upto ,end :by ,step :do (progn ,@body)) (loop :for ,var :from ,start :downto ,end :by (- ,step) :do (progn ,@body))) `(loop :for ,var :from ,start :upto ,end :do (progn ,@body)))))

(defmethod for-cm-expander ((fn (eql 'srfi-1:iota)) var list body whole) (declare (ignore whole)) (destructuring-bind (iota count &optional (start 0) (step 1)) list (declare (ignore iota)) `(loop :for ,var :from ,start :repeat ,count :by ,step :do (progn ,@body))))

こういう定義にすると、コンパイル時にはこういう展開になります。

(compiler-macroexpand '(for i (index 10 20) (print i)))
==>
(loop :for i :from 10 :upto 20 :do (progn (print i))) 

(compiler-macroexpand '(for i (srfi-1:iota 10) (print i))) ==> (loop :for i :from 0 :repeat 10 :by 1 :do (progn (print i)))

決め打ちのパターンから外れれば、リストを回す通常の処理になります。

(compiler-macroexpand '(for i (cons -1 (index 0 10)) (print i)))
==>
(for i (cons -1 (index 0 10)) (print i)) 

むすび

define-compiler-macroでマクロにコンパイラマクロを定義できるようにした経緯からユースケースを知りたかったのですが、過去のメーリングリスト等からは探し出せませんでした。

何か他のユースケースがあれば是非とも知りたいです。


HTML generated by 3bmd in LispWorks 7.0.0

LispWorks 7.1 購入への道(3) — バグ報告2件

Posted 2018-01-14 09:15:07 GMT

LispWorks 7.1を試用していて2件程バグっぽい挙動に遭遇していました。
折角試用させて貰ったので、何かバグ的なものは報告しておきたいところなので期間最終日に報告。

7.1は関数のボディで点対リストを受けつける

具体的な再現コードにすると

(defun foo (x) x . a)
→ foo

(compile nil (lambda (x) x . a)) → #<Function 15 406003A054> NIL NIL

というのが通ってしまいます。
LW 7.0では、 In a call to length of (x . a), tail a is not a LIST.

というエラーになりますが、LispWorks以外の大抵の処理系も同様のエラーとします。
7.1では、点対リストを許容するようになったのかとも思えますが、

(compile nil (lambda (x) . a))
→ Cannot take CDR of A.

こういうのは通らないので一貫性がない様子。

UTF-8のエンコーディング設定で起動時にエラーになる

もう一点、LispWorks 7.0を利用していてバグじゃないかと思いつつも適当なワークアラウンドで回避できていたのですっかり忘れていたことがありました。
具体的には、UTF-8の設定時に起動でコケるというちょっと嫌な感じのもの。

どうやらバイナリのファイル(スプラッシュ画像)をUTF-8で開こうとしてエラーになるらしいので、ファイルのエンコーディング判定関数で画像ファイルは迂回するようにしていましたが、これが7.1でも発生する様子。

;;; 回避コード
(defun utf-8-file-encoding (pathname ef-spec buffer length)
  (declare (ignore buffer length))
  (if (assoc (pathname-type pathname) graphics-ports::*file-types-to-image-types*
             :test #'equalp)
      '(:latin-1)
      (system:merge-ef-specs ef-spec '(:utf-8 :use-replacement t))))

(setq system:*file-encoding-detection-algorithm* '(utf-8-file-encoding))

報告を作成するためにちょっと追い掛けてみましたが、capi-gtk-library::put-image-data-in-fileでエラーになるらしいので、この関数のトレース結果を添付しました。

報告してみた

最終日に報告してみたところ、UTF-8の方はバグだったらしく二日後にプライベートパッチが届きました。
なんとパッチが出るとは想定していなかった。試用期間は終了しているのでパッチが試せないという……。

パッチの名前が、put-image-data-in-file.64ufaslなので、やはり報告した、capi-gtk-library::put-image-data-in-fileが問題のようです。

もったいないので、とりあえず駄目元で7.0で読み込ませてみましたが、faslに互換性がないと警告がでるものの、無視して続行可能なので試したら、7.0でも機能するようです。

ちょっと深追いして、パッチ適用前後のcapi-gtk-library::put-image-data-in-fileを比較してみると、修正前のものは、バイナリの一時ファイルを作成するのにsys:make-temp-fileというテキストの一時ファイルを作成する関数を呼んでいたため、エンコーディングの問題が発生していたようです。
修正では、sys:make-temp-fileの下請けであるsys::open-temp-fileを使ってファイルを(unsigned-byte 8)で開くようにした様子。

大抵のユーザーはlatin-1で使っていたから遭遇しなかった問題なのか、なんなのか。
とりあえず、バイナリファイルの開き方が悪かったということで、エンコーディング周りがバグってるわけではない様子なので良かったです。

ちなみに7.0で強制的に読み込ませるのには、

(handler-bind ((fasl-error #'continue))
  (scm:require-private-patch "put-image-data-in-file" :capi-gtk))

としています。

7.0用のパッチが貰えるなら、貰った方が良いのかもしれない。

試しにdisassembleの内容からコードを推測しつつ自作しててみましたが、下記と等価なようです。
(コンパイルオプションを設定すればdisassembleの結果が同じになる)

(in-package :cg-lib)

(declaim (optimize (safety 3) (speed 1) (float 1) (sys:interruptible 0) (compilation-speed 1) (debug 2) (hcl:fixnum-safety 3)))

(defun put-image-data-in-file (image-data) (destructuring-bind (file-type . data) image-data (let ((out (sys::open-temp-file :file-type file-type :element-type '(unsigned-byte 8) :external-format :latin-1))) (write-sequence data out) (close out) (namestring out))))

このコードを

(let ((source-debugging-on-p (source-debugging-on-p)))
  (toggle-source-debugging nil)
  (compile-file "put-image-data-in-file.lisp" :debug-info nil)
  (toggle-source-debugging source-debugging-on-p))

のような感じでコンパイルすれば、大体提供されているパッチファイルと同じになるようです。

LispWorks 7.0のパッチが入手できない場合は、最悪これでも良いかもしれません。

もう一つのボディが点対リストを許容する件については、一週間後位に回答があり、次期バージョンで善処したいということでした。

むすび

試用期間のバグ報告は、パッチが期間内に試せるように早めに報告しよう。


HTML generated by 3bmd in LispWorks 7.0.0

LispWorks 7.1 購入への道(2) — Common Prologを試す

Posted 2018-01-10 18:01:30 GMT

LispWorksでは、Enterprise版でしか使えない機能はいくつかありますが、中でも自分はKnowledgeWorksに興味があったので、まずはこれを試してみたいところ。

KnowledgeWorksは、Common Lispで構成された古き良き知識ベースのシステムで、主に後ろ向き推論のPrologと、前向き推論のOPS5が合体したようなシステムです。
1980年代後半から90年代前半の商用のLispシステムでは、エキスパートシステム作成用にOPS5やProlog拡張が付属することが多かったようで、こういう構成は案外定番の構成だった様子。
(ちなみに、Prologの処理系でも前向き推論の拡張等は良く付属していたようです。)

Prolog部分は、Common Prologと呼ばれていて、これ単体でも使えます。
ということで、このブログでは毎度お馴染のZebraベンチを書いてみました。

Common PrologでZebra ベンチ

;;;; -*- Mode: common-lisp; Syntax: Common-Lisp -*-
(declaim (optimize (speed 3) (safety 0) (compilation-speed 0)
                   (debug 0)))

(eval-when (:compile-toplevel :load-toplevel :execute) (require "prolog"))

(defpackage :clog-zebra (:use :cl :clog))

(in-package :clog-zebra)

(defrel nextto ((nextto ?x ?y ?list) (iright ?x ?y ?list)) ((nextto ?x ?y ?list) (iright ?y ?x ?list)))

(defrel iright ((iright ?left ?right (?left ?right . ?rest))) ((iright ?left ?right (?x . ?rest)) (iright ?left ?right ?rest)))

(defrel zebra ((zebra ?h ?w ?z) ;; Each house is of the form: ;; (house nationality pet cigarette drink house-color) (= ?h ((house norwegian ? ? ? ?) ;1,10 ? (house ? ? ? milk ?) ? ?)) ; 9 (member (house englishman ? ? ? red) ?h) ; 2 (member (house spaniard dog ? ? ?) ?h) ; 3 (member (house ? ? ? coffee green) ?h) ; 4 (member (house ukrainian ? ? tea ?) ?h) ; 5 (iright (house ? ? ? ? ivory) ; 6 (house ? ? ? ? green) ?h) (member (house ? snails winston ? ?) ?h) ; 7 (member (house ? ? kools ? yellow) ?h) ; 8 (nextto (house ? ? chesterfield ? ?) ;11 (house ? fox ? ? ?) ?h) (nextto (house ? ? kools ? ?) ;12 (house ? horse ? ? ?) ?h) (member (house ? ? luckystrike oj ?) ?h) ;13 (member (house japanese ? parliaments ? ?) ?h) ;14 (nextto (house norwegian ? ? ? ?) ;15 (house ? ? ? ? blue) ?h) (member (house ?w ? ? water ?) ?h) ;Q1 (member (house ?z zebra ? ? ?) ?h) ;Q2 ))

;; (logic '(zebra ?h ?w ?z) :return-type :fill) (defun zebra-benchmark (&optional (n 1000)) (declare (optimize (speed 3) (safety 0))) (let (rt0 rt1) (time (loop initially (setf rt0 (get-internal-run-time)) repeat n do (logic '(zebra ?h ?w ?z) :return-type :fill) finally (setf rt1 (get-internal-run-time)))) (destructuring-bind (houses water-drinker zebra-owner) (logic '(zebra ?houses ?water-drinker ?zebra-owner) :return-type :bag :bag-exp '(?houses ?water-drinker ?zebra-owner)) (values (/ (* n 12825) (/ (- rt1 rt0) 1000.0)) ; real time ; is milliseconds zebra-owner water-drinker houses))))

CL-USER 1 > (clog-zebra::zebra-benchmark)
Timing the evaluation of (LOOP CLOG-ZEBRA::INITIALLY (SETF CLOG-ZEBRA::RT0 (GET-INTERNAL-RUN-TIME)) COMMON-PROLOG:REPEAT CLOG-ZEBRA::N DO (COMMON-PROLOG:LOGIC (QUOTE (CLOG-ZEBRA::ZEBRA CLOG-ZEBRA::?H CLOG-ZEBRA::?W CLOG-ZEBRA::?Z)) :RETURN-TYPE :FILL) CLOG-ZEBRA::FINALLY (SETF CLOG-ZEBRA::RT1 (GET-INTERNAL-RUN-TIME)))

User time = 2.888 System time = 0.000 Elapsed time = 2.868 Allocation = 447980280 bytes 0 Page faults 4440789.5 CLOG-ZEBRA::JAPANESE CLOG-ZEBRA::NORWEGIAN ((CLOG-ZEBRA::HOUSE CLOG-ZEBRA::NORWEGIAN CLOG-ZEBRA::FOX CLOG-ZEBRA::KOOLS CLOG-ZEBRA::WATER CLOG-ZEBRA::YELLOW) (CLOG-ZEBRA::HOUSE CLOG-ZEBRA::UKRAINIAN CLOG-ZEBRA::HORSE CLOG-ZEBRA::CHESTERFIELD CLOG-ZEBRA::TEA CLOG-ZEBRA::BLUE) (CLOG-ZEBRA::HOUSE CLOG-ZEBRA::ENGLISHMAN CLOG-ZEBRA::SNAILS CLOG-ZEBRA::WINSTON CLOG-ZEBRA::MILK CLOG-ZEBRA::RED) (CLOG-ZEBRA::HOUSE CLOG-ZEBRA::SPANIARD CLOG-ZEBRA::DOG CLOG-ZEBRA::LUCKYSTRIKE CLOG-ZEBRA::OJ CLOG-ZEBRA::IVORY) (CLOG-ZEBRA::HOUSE CLOG-ZEBRA::JAPANESE CLOG-ZEBRA::ZEBRA CLOG-ZEBRA::PARLIAMENTS CLOG-ZEBRA::COFFEE CLOG-ZEBRA::GREEN))

以前、Common Lispの埋め込みPrologを試してみる: Zebraベンチ篇で、ベンチ対決した結果と並べるとこんな感じです。

順位 処理系 タイム(秒)
1 AZ-Prolog 0.710 1
2 Allegro Prolog 0.852 1.2
3 SWI-Prolog 1.422 2
4 Common Prolog 2.888 4
5 PAIProlog 11.712 16.5
6 Uranus 51.080 71.9

むすび

PAIPrologの約6倍速く、Allegro Prologの3.4倍遅く、最速のAZ-Prologからは約4倍遅いという結果になりました。
Allegro Prologが謎の速さをみせていますが、Common Prolog(KnowledeWorks)は多機能で、開発環境もリッチですし、全体的なバランスとしては、なかなか良いのではと感じています。

関連エントリー


HTML generated by 3bmd in LispWorks 7.0.0

LispWorks 7.1 購入への道(1)

Posted 2018-01-08 16:48:54 GMT

昨年末の11月13日、LispWorks 7.1が発表されました
LispWorks 7.0が2015-05-05の発表だったので、約二年半ぶりのバージョンアップです。

LispWorks 7.0を使い始めて二年ちょっと経過しましたが、毎日使っているとはいえ、あまり使い倒した感もないため、このまま7.0で行くか、とりあえず、7.1に上げるか悩ましい所ですが、一ヶ月試用できるサービスがあるので、出てすぐの11月17日に試用を申し込んでみることにしました。

お察しの通り、もうとっくに試用期間も終わってしまっているのですが、7.1について何一つブログに書いていなかったので何回かに分けて書いてみます。

試用の申し込み

試用については、二年前のエントリーと全く同じ手順でしたが、既存ユーザーだからか毎度ありがとうというような返事が来ました。

前回は、HobbyistDVでの試用でしたが、今回は、折角なので、HobbiestとEnterpriseの二種で申し込んでみました。

格が違うとはいえ、ライセンスキーが違うのみで、配布されているファイルは全バージョン共通のようです。

ライセンスキーが届いたので、ファイルをダウンロードし、早速、以前報告したバグがどうなっているか確認してみました。

7.0でのバグは修正されたのか

さて、7.0を使っていて、何度かバグ報告をしてパッチを貰ったりはしていましたが、修正はされているのかが気になりますので、早速確認。

一応、リリースノートには目を通しましたが、そんなに細かいものまで書いてはいないようです。

シンボルのバグ: 修正済み

7.0では、UTF-8環境で、(eq '資料コード '資料コード) → nilだったりしたので、報告してプライベートパッチを貰ったりしていましたが、治っていました。これは良かった。
しかし、これ7.0で公開パッチ出して欲しかった所ですなあ。

libssl 1.1の読み込みに失敗する: 対応済み

libssl 1.1を読み込ませる場合に明示に指定しないといけない件で問い合わせましたが、次バージョンで対応とのことでした。
リリースノートにもある通り対応されていたので良かったです。

システムの内部関数で型宣言が合ってないものがある: 据置き

sys::find-external-symbolの型宣言が合ってないのでコンパイルするとエラーになる件でしたが、内部関数は使うな、という回答を貰っていました。
確認した所こちらは据置き。
プロダクション用コンパイルのオプションだと無視しちゃうから気にしてない、とかそういう感じだとは思うけど、いやでも変だと思うんだよなあ。

その2に続く

関連エントリー


HTML generated by 3bmd in LispWorks 7.0.0

逆引きCommon Lisp/Scheme・Common Lisp Users JPサイトを移動しました

Posted 2018-01-05 21:59:04 GMT

立ち上げて早十年の逆引きCommon Lisp/Schemeサイトですが、Shibuya.lisp時代には長期間サイトが落ちていてもなかなか復旧されないため個人のサーバーに移動してみたり転々としています。
先日、再び新しいサーバーに移動することになったので、このタイミングでLispの情報集積サイトである lisphub.jpに移動することにしました。
さらについでで、2010年に立ち上げた Common Lisp Users JP も移動し、若干URLに統一感を持たせる感じで一緒のサイトに設置しました。

旧URLからは301でリダイレクトされるのでリンクから辿ってくるぶんには使い勝手に変化はありません。

ちなみに、 lisphub.jp ですが、最低10年は保たれるLispのハブサイトを目指して、2013年に立ち上げましたが、広報活動で色々失敗しており、2023年までドメインだけは確保しているという悲しい状況なので今後は活用していきたい所存です……。


HTML generated by 3bmd in LispWorks 7.0.0

2017年振り返り

Posted 2018-01-03 10:24:45 GMT

毎年振り返りのまとめを書いていたのですが、ホストしているサーバーの移行をしていて、2017年中に間に合いませんでした。
このブログを配信しているteepeedee2が上手く動かせなかったというのが主な理由ですが、teepeedee2は毎度ながら手強い……。

Lisp的進捗

ブログ

ですます調をやめて、である調にしましたが、どうもしっくり来ないので、今年からはまた、ですます調に戻します……。

である調でも書けるようになってみたかったのですが、どうも過剰に偉そうな雰囲気になってしまうという。

学習

毎年、Advent Calendarで少しだけ無理をして何かを調べて書いていましたが、2017は気力がないのでやめてしまいました。
しかし、やっぱり何かやっておけば良かったなあという思いが残る……。
2018年はがんばりましょう。

LispWorks

LispWorks 7.1が2017年11月に登場し、自分も試用を申し込んで、それなりに7.1の知見は貯まりましたが、全然記事にしていません。
7.0からHobbyist Edition($750)が登場し以前よりは、身近になった気はしますが……。

去年読んでる途中と報告した、LispWorks User Guide and Reference Manualとか、CAPI User Guide and Reference Manualは未だに読み終えていません。

仕様とかマニュアルを読み通すって結構大変なんですよねえ……。 なんかしらちょっとは書きたいと思います。

来年やってみたいこと

昨年の目標であった、

  • 積極的にLisp本の積読本を読んで記事にして行きたい
  • LispWorksのマニュアル全部を通読したい
  • VMS/VAXのエミュレータでVAX LISPを動かしたい
  • Lisp組み込み系Prologを系統立てて比較してみたい
  • Shenをもうちょっと触りたい
  • ヒューイット先生について調べる

は、VAX LISPがライセンスの関係で動かなかったことがはっきりした以外は何も進捗がないですね。
いやあ、時間がない訳では全くないのですが……。

やりたいことは大して変化なしなので、継続としたいと思います。
今年は、もうちょっと活動的になりたいですね。

過去のまとめ


HTML generated by 3bmd in LispWorks 7.0.0

マクロ禁止令

Posted 2017-12-13 11:52:38 GMT

Lisp Advent Calendar 2017 十三目です。
空きがあったのでネタで埋めようと思い書きました。
十三目書いてたのに!という方は、すいませんが、まだ空きがあるので他の日を埋めてください。

マクロが禁止されたらどうなるの

良くも悪くも誤解が多いLispマクロ。
Lispマクロに心酔するあまり過大評価する人もいれば、過大評価する人をみて過小評価に転ずる人もいる始末ですが、基本的にはコードを生成するだけの機能です。

そんなマクロですが、Common Lispで仮に禁止されたらどうやって生きていったら良いのか考えてみました。
(ちなみに話を簡単にするためにローカルマクロのことは考えないことにします。)

defmacroは何をしているの

Common Lispのdefmacroで定義するものは、リストを引数にして、リストを返すという関数です。
しかし、評価の前に再帰的にマクロを展開するフェイズがあり、そこで展開関数が実行されるので、まるで関数評価のような感じで使うことができます。

例えば、下記のようなコードでloopの展開関数だけ実行することも可能です。

(funcall (macro-function 'loop)
         '(loop :for i :from 0 :repeat 10 :collect i)
         nil)

関数だけでdefmacroのようなことをしてみる

例としてdotimesのようなものを考えてみましょう

'(dotimes (i 10) (princ i))

のようなリストを

'(prog ((#:|limit17589| 10) (i 0))
      "=>"
      (cond ((<= #:|limit17589| i) (return (let ((i nil)) nil))))
      (progn (princ i))
      (incf i)
      (go "=>"))

のようなリストに変形すれば良いので、

(defun mydotimes (form &optional env)
  (declare (ignore env))
  (destructuring-bind (_ (var limit &optional result) &body body)
                      form
    (declare (ignore _))
    (let ((limvar (gensym "limit"))
          (tag (gensym "=>")))
      `(prog ((,limvar ,limit)
              (,var 0))
        ,tag (cond ((<= ,limvar ,var)
                    (return (let ((,var nil)) ,result))))
             (progn ,@body)
             (incf ,var)
             (go ,tag)))))

のような関数を書けるでしょう。
(まあ、結局の所マクロを書く作法が身に付いていないと、こういう関数も書けないのですがそれは一旦忘れましょう)

これで、下記のように書けます。

(with-output-to-string (out)
  (declare (special out))
  (eval (mydotimes 
         `(mydotimes (i 3)
            ,(mydotimes '(mydotimes (j 3) 
                           (princ i out)
                           (princ j out)))))))
→ "000102101112202122" 

やはりマクロ展開と実行コードを混ぜて書かないといけないので、ごちゃごちゃしてしまいます。
別個にマクロ展開関数を用意して、オペレーターが定義したマクロかどうかを確認しつつ展開するようにすれば、

(with-output-to-string (out)
  (declare (special out))
  (mexpand `(mydotimes (i 3)
              (mydotimes (j 3) 
                (princ i out)
                (princ j out)))))
→  "000102101112202122" 

位には圧縮できるかもしれません。

もうちょっと綺麗にできないか

とりあえずは、安直に簡単に見た目を変える方向で、リーダーマクロを使ってごちゃごちゃを隠してみましょう。
見た目がごちゃごちゃしているだけではなく、上記では、変数の結合も実行時にしているので、変数をダイナミック変数に指定していたりします。この辺りもリーダーマクロで読み取り時に展開してしまえば解決です。

なお、リーダーマクロも禁止ならファイルを2パスで処理する等々しかないですね。

(set-syntax-from-char #\] #\))
(set-macro-character 
 #\[ 
 (lambda (s c)
   (declare (ignore c))
   (let ((form (read-delimited-list #\] s T)))
     (funcall (car form) form))))

(with-output-to-string (out)
  [mydotimes (i 3)
    [mydotimes (j 3)
      (princ i out)
      (princ j out)]])
→ "000102101112202122" 

結論

結局の所、

  • コードがデータ
  • 評価器に渡るコードを変形するフックがユーザーに開放されている

の2点が言語に備わっていれば、Lispマクロのような機能と使い勝手は実現可能だということが分かるでしょうか。
特にLispには限らない筈ですが、使い勝手を含めて真面目に活用が考えられてきた、また実績があるのは、ほぼLisp系言語のみ、というのが現状だと思います。

誕生当初は、LispもM式→S式の変換をして実行するものと考えられていたLispですが、S式というデータの世界にLispプログラマが飛び込んだことが偉大だったのかもしれません。


HTML generated by 3bmd in LispWorks 7.1.0

世界から括弧が消えたなら

Posted 2017-12-07 17:20:25 GMT

Lisp Advent Calendar 2017 八日目です。
空きがあったのでネタで埋めようと思い書きました。
八日目書いてたのに!という方は、すいませんが、まだ空きがあるので他の日を埋めてください。

括弧が見えなくなったらどうなるの

Emacs等のエディタでは括弧だけ薄い色にしたりできるようですが、Unicodeの幅がない文字で置き換えたらどうでしょうか。

UTF-8のCommon Lispならこんな感じの設定にすればOKでしょう

(progn
  (set-macro-character
   (code-char #x200C)
   (lambda (s c)
     (declare (ignore c))
     (read-delimited-list (code-char #x200D) s T)))
  (set-syntax-from-char (code-char #x200D) #\)))

そうしたら、こう書けます。

‌defun fib ‌n‍
  ‌if ‌< n 2‍
     n
     ‌+ ‌fib ‌1- n‍‍
       ‌fib ‌- n 2‍‍‍‍‍

‌fib 10‍ → 55

いやあ、読み難いなあ。

エディタに支援してもらおう

Emacsには文字に構文上の意味が持たせられるので、設定しておくと便利かもしれません。

(progn
  (modify-syntax-entry 8204 (format "%c%c" 40 8205))
  (modify-syntax-entry 8205 (format "%c%c" 41 8204)))

これで若干編集が効きますが、どうもあまり上手くいかない。

見えない括弧の応用例

Clojureっぽく書きたい人へ

Clojureは括弧が少ないんだ優勝だという人は、こういう感じはどうでしょうか。

(defun fib (n)
  (cond(< n 2) n‍
        ‌:else (let (‌n1 (1- n)‍
                    ‌n2 (- n 2))
                (+ (fib n1)
                   (fib n2)))))

まあまあですね。

オリジナルのポーランド記法にこだわる

Lispの表記法は、括弧を明示するので、Cambridge Polish Notationなどとも呼ばれます。
しかし、オリジナルのポーランド記法は、アリティが決まっているなら括弧を不要にできることこそがその特長だったようです。
その意向を汲んでみました。

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

アリティを暗記していないといけないので、読み書きで脳に負担がかかりそうですね。

結論

括弧は脳に優しい。


HTML generated by 3bmd in LispWorks 7.1.0

Common Lisp と タイムゾーン と Zetalisp

Posted 2017-12-04 15:00:01 GMT

Lisp Advent Calendar 2017 五日目です。

Common Lisp は移動体上でのタイムゾーンを意識して設計されている?

Common Lispのタイムゾーンについては移動体のことを考慮し、定数になっていないというような話が前々から気になっていたので、実際のところどうなのだろうと思って調べてみた。

元ネタ

多分、元ネタはCLtL2なんだろうと思うので検索してみると、CLtL2のget-decoded-timedecode-universal-timeの注釈にある、

Compatibility note: In Lisp Machine Lisp time-zone is not currently
returned. Consider, however, the use of Common Lisp in some mobile
vehicle. It is entirely plausible that the time zone might change from
time to time.

だと思われる。

この注釈の解釈だけれど、Common Lispがタイムゾーンを意識してどうのこうのしたというよりは、互換性にかこつけて、Zetalispがタイムゾーンを返さないことについて細かいツッコミをいれているように思えるのだがどうだろうか。

CLtL1も確認してみたら、同様の記述だったので、1984年時点の認識らしい。

移動体であればCommon Lisp処理系は結局ホストのOSかどこかからタイムゾーンの情報を貰ってくることになるが、基盤となるuniversal-timeは1900-01-01T00:00Zからの秒数なので基本的にこれが動くこともなく、どこのタイムゾーンとしてデコードするか、という話になる。
ちなみに、今どのタイムゾーンにいるかを処理系がホストと通信したりして把握する手順/機構のようなものはCommon Lispの規格上には定義されていない。

文句を付けられていたZetalisp(Lisp Machine Lisp)は実際どうだったのか

それで、実際Zetalisp(Lisp Machine Lisp)がどうだったのかをソースで確認してみたが、1982年頃のSystem 78.48では確かにタイムゾーンは返していなかった。
しかし、1984年のSystem 99のソースとその時期のマニュアルであるChinual 6ではタイムゾーンを返すようになっているので、CLtL1が出版された前後で既にCLtL1のZetalispとの互換性の注釈は、時代遅れなものになっていたらしい。

1984年は、MIT LispMもCommon Lisp化した時期なので、ZetalispがCommon Lispを取り込んでしまった、もしくは、ZetalispがCommon Lisp化した、とも考えられるので微妙ではあるが……。
1983年のChinual 5を眺めるとまだタイムゾーンは返していないようなのでCLtL1執筆時のツッコミがChinual 6に影響したのかもしれない。

しかし、1990年に出版されたCLtL2でも、この趣味的な記述がアップデートされることもなく、Zetalispはタイムゾーンを返さないといわれっぱなしで今に至ることになったらしい。

ちなみに、Zetalispでもuniversal-timeへのエンコードについては1980年より前からタイムゾーンは指定する仕様になっているので、デコードされたuniversal-timeから元のuniversal-timeが復元できない非対称性についてのツッコミであったかもしれない。

まとめ

Lisp関係の伝説においては、誰も気にしないので検証もされないような趣味的なものが延々と語り継がれることって多い気がする。
折角なのでスマホ上の処理系でGPSからデータを取得してuniversal-timeをデコードするようなものがあったら面白いかもしれない。


HTML generated by 3bmd in LispWorks 7.1.0

Hexstream CLOS MOP Specの紹介

Posted 2017-11-30 17:40:58 GMT

今年も始まりました、Lisp Advent Calendar 2017 第一日目です。
今回は、Hexstream CLOS MOP Specを紹介します。

Hexstream CLOS MOP Specというのは私が勝手に命名したものなので正式名称でもなんでもないのですが、 HexstreamことJean-Philippe Paradis氏がまとめたCLOS MOPのオンラインリファレンスです。

正式名称は、Common Lisp Object System Metaobject Protocol と素材そのままのようですね。

CLOS MOPとはなんぞや

そもそもCLOS MOPとは、ということになるのですが、Common Lispのオブジェクトシステムは、オブジェクト指向プログラミングによって、ユーザーがカスタマイズ可能です。
インスタンスの生成、スロット(メンバ変数)アクセス、継承の仕方等々が、プロトコルとしてまとめられているのですが、操作対象は通常のオブジェクトから一段階メタになってメタオブジェクトとなります。
通常のオブジェクトが車だとしたら、車を組み立てるロボット(仕組み)が更にまたオブジェクトになっていて色々いじれるという感じです。
車を組み立てるロボットをカスタマイズすることにより、車の作り方や、作られる車の構成をカスタマイズ可能、くらいの所でしょうか。

CLOS MOPでのメタオブジェトは総称関数、メソッド、スロット定義、メソッドコンビネーション、スペシャライザ等がありますが、Hexstream CLOS MOP Spec ではすっきり図になっているので、どんな感じの構成になっているかわかりやすいと思います。

CLOS MOPの仕様について

残念ながらこのCLOS MOPですが、ANSI Common Lispの規格には組込まれることはありませんでした。
割と早い段階からCLOS三部構成のうち、三番目に位置するものとされ、仕様も詰められていましたが、1990年代初頭当時としては野心的過ぎたのか、うちの処理系ではMOPはサポートしないと明言するような主力ベンダーも現われたり、紆余曲折あって、最初の二部までがANSI Common Lispとして規格化される、という流れになりました。

規格としてはまとまらなかったものの、その前の段階の準備や成果が、The Art of the Metaobject Protocolという書籍としてまとめられます。
(正確には同時進行ですが)
通称AMOPとして有名な本ですが、この本の5章と6章はCLOS MOPの仕様がまとめられた章で、この仕様の部分はオンラインでも公開され、CLOS MOPの仕様といえば、この公開されたAMOPの仕様部分ということになっています。

このAMOPのCLOS MOP仕様部分は、TeX等の形式で配布されていましたが、1997年に当時Eclipse Common Lispを作っていた Elwood Corporation の Howard R. Stearns氏がHyperSpecに似た感じのhtml形式にして公開しました。

これが広く長らく使われていて、Franzなどもマニュアルの一部として配布しています。

しかし、Elwood版は、それほど使い勝手が良いとはいえず、改良するにもライセンスとして改変不可だったりするので、元のTeXからhtmlを仕立てる人が出てきたという所で、Robert Strandh氏や、今回紹介するHexstream氏のバージョンがそれにあたります。

Strandh氏のものはシンプルにまとめてあり、さらに注釈も添えられ、HyperSpecへのリンクもあるので、読み進めるのに便利です。
このStrandh氏のまとめたものを更に体裁よくまとめたものが、Hexstream氏のバージョン、というところです。

たとえば、Hexstream氏のものはプロトコルごとに眺められたりして、なかなか良いです。

また、関数名がidになっているので、Emacs等からドキュメントを検索するのも楽です。
簡単な関数を書けば、ブラウザで開くことも可能でしょう。

(defun amop-lookup (&optional symbol-name)
  (interactive)
  (let ((name (or symbol-name
                  (thing-at-point 'symbol))))
    (setq name (subseq name (1+ (or (string-match ":" name) -1))))
    (browse-url
     (format "https://clos-mop.hexstreamsoft.com/generic-functions-and-methods/#%s"
             name ))))

(c2mop:effective-slot-definition-class ...)

のようなシンボルの上で、M-x amop-lookupすれば、該当のページが開きます。

まとめ

今回は、Hexstream CLOS MOP Specを紹介しました。

とっつきにくいAMOPのリファレンスですが、綺麗にまとまっていると読み進めるのが楽で良いですね。


HTML generated by 3bmd in LispWorks 7.1.0

ボディが無限リスト 其の二

Posted 2017-11-15 12:02:15 GMT

大分古くからあるものだけれど、Olin Shivers氏が式のボディ部が無限リストになっているという面白いアイデアを書いていて、以前ちょっと考えてみたりした。

無限リストに関数を詰めて呼んでみたり、という感じだったけれども、より直接的には、evalが使えるなと考えた。

なお、評価はちょっと間違うと無限ループになるので注意。

(progv '(i) '(0)
  (catch :exit
    (eval
     '(progn . #0=((when (= 1000 i) (throw :exit :done))
                   (print i)
                   (incf i)
                   . #0#)))))

以上、暇だったので。


HTML generated by 3bmd in LispWorks 7.0.0

続ClaspがSBCLより速くなったと聞いて

Posted 2017-11-03 19:13:46 GMT

昨日のエントリーで「なぜかbench-stringというベンチでClaspがズバ抜けて速い」と書いたが、Twitterでこの件について反応があった。

BENCH-STRING が速いの、最適化でその部分のコードがごそっと削除されてるからとかいうオチがありそうな。
Kei @tk_riple

なるほど、最適化によるコード削除疑惑。
Common Lispの場合、最適化した際にdotimes等で返り値を使わない場合によく消えたりはするかもしれない。
しかし、消えてる感じにしては遅いので、まあまあそんなものかなと思っていた。
また、現状のClaspはまだ最適化がどうの、というより、まずは正しく動かすフェイズな気がするので、そんなに最適化もがんばってはいないという印象を持っていた。
実際、最適化の指定をしても型のヒントを与えても全然効いてないように思う。

結論: Claspのバグが原因で実行されないコードがあったため速かった

結論から書いてしまうと、Claspのfillのバグが原因でfill以降が実行されない為に速かった。
なので、最適化ではないけれど、searchの部分のコードが削除されていた状態になっていた。
一応順を追って説明してみる。

まず、元のコードについて。

(defun bench-strings (&optional (size 1000000) (runs 50))
  (declare (fixnum size))
  (let ((zzz (make-string size :initial-element #\z))
        (xxx (make-string size)))
    (dotimes (runs runs)
      (and (fill xxx #\x)
           (replace xxx zzz)
           (search "xxxd" xxx)
           (nstring-upcase xxx))))
  (values))

同じ長さの大きい文字列を2つ作ってfillで‘x’で埋め、もう片方でreplaceし、“zzzz…”という文字列にしてしまう。
それをsearchで“xxxd”について検索するが見付からないので、nstring-upcaseは実行されない、という流れ。

andで繋いでいるのは、指摘があったような最適化でコードが消えるのを防いでいるのかもしれない。

とりあえず、dotimesの中身を全部消したものと比較すると、全部消したものがずっと速いので、中身を全部消しているということはなさそう。

そこで一つずつ足していってみたが、そこでClaspのvectorに対してのfillの返り値がnilであることに気が付いた。
仕様では、fillsequenceを返すことになっているが、nilが返ってしまうとandで繋いでいるだけに以降が実行されないことになってしまう。

Claspのソースを確認してみると、

(defun fill (sequence item &key (start 0) end)
  ;; INV: WITH-START-END checks the sequence type and size.
  (reckless
   (with-start-end (start end sequence)
     (if (listp sequence)
         (do* ((x (nthcdr start sequence) (cdr x))
               (i (- end start) (1- i)))
              ((zerop i))
           (declare (fixnum i) (cons x))
           (setf (first x) item))
         (si::fill-array-with-elt sequence item start end)))))

となっていて、vectorの場合は、si::fill-array-with-eltの返り値となるが、そのsi::fill-array-with-eltはClaspらしくC++で書かれていた。

/*! Fill the range of elements of the array,
   if end is nil then fill to the end of the array*/
CL_LISPIFY_NAME("core:fill-array-with-elt");
CL_DEFUN void core__fillArrayWithElt(Array_sp array, T_sp element, cl_index start, T_sp end) {
    size_t_pair p = sequenceStartEnd(core::_sym_fillArrayWithElt,
                                     array->arrayTotalSize(),start,end);
    array->unsafe_fillArrayWithElt(element,p.start,p.end);
  }

core__fillArrayWithEltvoidなので、多分CLの世界ではnilを返すことになるのだろう。

ということで、

(defun fill (sequence item &key (start 0) end)
  ;; INV: WITH-START-END checks the sequence type and size.
  (reckless
   (with-start-end (start end sequence)
     (if (listp sequence)
         (do* ((x (nthcdr start sequence) (cdr x))
               (i (- end start) (1- i)))
              ((zerop i))
           (declare (fixnum i) (cons x))
           (setf (first x) item))
         (si::fill-array-with-elt sequence item start end))
     sequence)))

(fill (make-string 42) #\*) → "******************************************"

のように修正し、再度SBCLと比べてみた

バグを修正して再計測: 最適化指示ありなしで比べてみる

最適化指示なしだと依然としてClaspの方が3倍位速いらしい。
しかし、指示ありだと、SBCLがClaspの2倍位速くなった。
一応SBCLの方のdisassemble結果を確認したが、中身がごっそり消されているということはなかった。

Claspの方は、最適化指示ありでもなしでもあまり変わらず。
従来のCL処理系は、普段は遅めだけど、追い込むと速い、という傾向があるが、Claspは、普段から速めで、追い込んでもそんなに速くならない系になっていくのかもしれない。

なお、一応Claspにバグ報告は出してみた。

SBCL

(bench-strings)
;=> nil
#|------------------------------------------------------------|
Evaluation took:
  0.595 seconds of real time
  0.594982 seconds of total run time (0.594982 user, 0.000000 system)
  100.00% CPU
  1,958,880,795 processor cycles
  8,000,064 bytes consed

Intel(R) Xeon(R) CPU E3-1230 v3 @ 3.30GHz |------------------------------------------------------------|#

Clasp

(bench-strings)
;=> nil
#|------------------------------------------------------------|
Real time           : 0.208 secs
Run time            : 0.208 secs
Bytes consed        : 8026792 bytes
LLVM time           : 0.000 secs
LLVM compiles       : 0
clang link time     : 0.000 secs
clang links         : 0
Interpreted closures: 0
nil
 |------------------------------------------------------------|#

SBCL 最適化指示あり

(defun bench-strings+ (&optional (size 1000000) (runs 50))
  (declare (optimize (speed 3) (safety 0) (debug 0)))
  (declare (fixnum size runs))
  (let ((zzz (make-string size :initial-element #\z))
        (xxx (make-string size)))
    (declare (simple-string xxx zzz))
    (dotimes (runs runs)
      (and (fill xxx #\x)
           (replace xxx zzz)
           (search "xxxd" xxx)
           (nstring-upcase xxx))))
  (values))

(bench-strings+) ;=> nil #|------------------------------------------------------------| Evaluation took: 0.104 seconds of real time 0.103990 seconds of total run time (0.103990 user, 0.000000 system) 100.00% CPU 342,637,002 processor cycles 8,000,064 bytes consed

Intel(R) Xeon(R) CPU E3-1230 v3 @ 3.30GHz |------------------------------------------------------------|#

Clasp 最適化指示あり

(proclaim '(optimize (speed 3) (safety 0) (debug 0)))

(defun bench-strings+ (&optional (size 1000000) (runs 50)) (declare (fixnum size runs)) (let ((zzz (make-string size :initial-element #\z)) (xxx (make-string size))) (declare (simple-string xxx zzz)) (dotimes (runs runs) (and (fill xxx #\x) (replace xxx zzz) (search "xxxd" xxx) (nstring-upcase xxx)))) (values))

(bench-strings+) ;=> nil #|------------------------------------------------------------| Real time : 0.222 secs Run time : 0.222 secs Bytes consed : 8026792 bytes LLVM time : 0.000 secs LLVM compiles : 0 clang link time : 0.000 secs clang links : 0 Interpreted closures: 0 nil |------------------------------------------------------------|#


HTML generated by 3bmd in LispWorks 7.0.0

ClaspがSBCLより速くなったと聞いて

Posted 2017-11-02 19:05:04 GMT

Shibuya.lisp Lispmeetup #57 の発表でClaspがSBCLより速かったりするらしいというのを耳にして、いつの間にかそこまで進歩してたのかと思ったので早速自分も試してみることにした。

ビルドは、本家のWikiの通りに実行し特に問題もなくビルドはできた。ただ時間は、3時間程度掛った。
Clasp 0.5 Build Instructions

cl-benchで計測してみる

3年位前にclaspが登場した頃に、一度clasp 0.2でcl-benchを実行してみたが、完走できない項目ばかりだった。
今回のclasp 0.5で試してみたところ大体の項目が完走できた。しかし物によってはSegmentation faultで処理系ごと落ちたりもする。
cl-bench は、Symbolics CLや、Lucid CLでも走る位なので可搬性は高い、というか規格内の機能だけで書いてある(多分)。

cl-benchや手元で確認してみる感じでは、SBCLより速かったりすることは無さそうに思えた。
さらに安定性については、まだ比較対象にならないという感じ。

目につくところでは、なぜかbench-stringというベンチでClaspがズバ抜けて速い。
bench-stringの定義はこんな感じ

(defun bench-strings (&optional (size 1000000) (runs 50))
  (declare (fixnum size))
  (let ((zzz (make-string size :initial-element #\z))
        (xxx (make-string size)))
    (dotimes (runs runs)
      (and (fill xxx #\x)
           (replace xxx zzz)
           (search "xxxd" xxx)
           (nstring-upcase xxx))))
  (values))

全体的な印象としては、依然としてClaspはまだまだ開発中という感じがした。
ClaspはC++との連携が最大の強みだと思うが、自分はC++の資産を使うこともないので、UTF-8をサポートするまで様子見でも良いかなというところ。
ちなみに、個人的には、SICLのコードが全面的に使われるらしいというところに興味がある。現状は、まだまだECLのコードが多い様子。

下記にベンチ結果を載せてみる。
なお、ECLやClaspと同じくSBCLはGMPを有効にしてある。 使用したマシンは、CPUが、Intel(R) Xeon(R) CPU E3-1230 v3 @ 3.30GHz でメモリは32GiB

ベンチ


HTML generated by 3bmd in LispWorks 7.0.0

再帰的に自己を一回だけインライン展開する

Posted 2017-10-29 23:34:38 GMT

日立が開発していたメインフレーム用Common LispであるHiLISP(VOS3 LISP)についての記事・知識処理用言語HiLISPの高速化方式 — 日立評論 1987年3月号を読んでいて、再帰呼び出しを一回だけインライン展開するという方法が紹介されているのを目にした(記事では、自己再帰展開と呼んでいる)。

面白そうなのでこの自己再帰展開™というものを再現してみることにした。

最近のCommon Lispの処理系ではインライン指定すれば再帰的に展開してくれるものもあれば、そうでないものもあり、展開の仕方もまちまちだけれどもfletで関数内関数を定義し、それにインライン指定すれば簡単に実現できると思われる。

(defmacro defsubstself (name (&rest args) &body body)
  `(defun ,name (,@args)
     (flet ((,name (,@args)
              ,@body))
       (declare (inline ,name))
       ,@body)))

(defsubstself fib/ (n)
  (declare (optimize (speed 3) (safety 0) (debug 0)))
  (declare (fixnum n))
  (the fixnum
       (if (< n 2)
           n
           (+ (fib/ (1- n))
              (fib/ (- n 2))))))
===>
(defun fib/ (n)
  (flet ((fib/ (n)
           (declare (optimize (speed 3) (safety 0) (debug 0)))
           (declare (fixnum n))
           (the fixnum (if (< n 2) n (+ (fib/ (1- n)) (fib/ (- n 2)))))))
    (declare (inline fib/))
    (declare (optimize (speed 3) (safety 0) (debug 0)))
    (declare (fixnum n))
    (the fixnum (if (< n 2) n (+ (fib/ (1- n)) (fib/ (- n 2)))))))

fletの関数定義部のボディの中で自己を参照することはないので、一回だけ展開させるには都合が良い。

自己再帰展開™fibと通常のfibを比べるとLispWorksでは25%程高速化した。
めでたしめでたし。

解決かと思ったが……

fletを使えばできると思ったが、しかし、大域関数をfuncallした場合は、どうなるだろう。
もちろんfletで作った関数は大域関数ではないので呼ぶことはできない。
具体的には下記のような場合。

(defun fib (n)
  (declare (optimize (speed 3) (debug 3) (safety 0)))
  (declare (fixnum n))
  (the fixnum
       (if (< n 2)
           n
           (+ (funcall 'fib (1- n))
              (funcall 'fib (- n 2))))))

そもそも、こういう場合は、インライン展開はどうなるのだろうか。
インライン展開してくれそうな処理系で調べてみると、fibinline宣言がされた場合の挙動は、

処理系 funcall 'fn funcall #'fn
LispWorks 展開する 展開する
SBCL 展開しない 展開する
Allegro CL 展開しない 展開しない

という風に、展開したりしなかったりの様子。
funcall 'fnでもコンパイル時にはインライン指定のスコープはfuncall #'fnと同様に確定できると思われるので、SBCLが展開しないのはちょっと不思議。

3.2.2.3 Semantic Constraints にも

Within a function named F, the compiler may (but is not required to)
assume that an apparent recursive call to a function named F refers to
the same definition of F, unless that function has been declared
notinline. The consequences of redefining such a recursively defined
function F while it is executing are undefined.

とあるので、コンパイル時に確定はできそうだけれど、an apparent recursive call to a function named Ffuncall 'fn形式が含まれるのかは良く分からない。
(functionの定義からして、funcall #'functionは含まれるだろう)
もしかすると、LispWorksがやりすぎなのかもしれない。

ちなみに、Allegro CLはインラインについて一家言あるようなので展開しないらしい。

LispWorksでは、funcall 'fnでもインライン展開するので、大域の補助関数を作成して、それを呼び出せば良さそうだけれど、SBCLではそれでは駄目なので、結局はコードウォーキングしないといけないらしい。

ということで処理系に備わっているwalk-formを使って下記のようなものを書いてみた。

(import (find-symbol (string '#:walk-form)
                     #+allegro :excl
                     #+sbcl :sb-walker
                     #+lispworks :walker))

(defun replace-fn (fsym replace form env) (let ((mark (gensym "mark"))) (subst replace mark (walk-form form env (lambda (sub cxt env) (declare (ignore cxt env)) (when (and (consp sub)) (when (eq fsym (car sub)) (setf (car sub) mark)) (when (and (eq 'function (car sub)) (eq fsym (cadr sub))) (setf (cadr sub) mark)) (when (or (eq 'funcall (car sub)) (eq 'apply (car sub))) (when (and (eq 'quote (caadr sub)) (eq fsym (cadadr sub))) (setf (caadr sub) 'function) (setf (cadadr sub) mark)))) sub)))))

(defmacro defsubstself (name (&rest args) &body body &environment env) (replace-fn name `(lambda (,@args) ,@(copy-tree body)) `(defun ,name (,@args) ,@body) env))

これは、自分の関数名の所をlambdaで置き換えてしまうので下記のような展開になる。
(なお、一度シンボルで置き換えてからlambdaフォームに直しているのは、walk-formが置き換えたフォームを更に展開し展開が止まらなくなるため。)

追記(2018-02-20)

walk-formがとる関数引数は多値を返すことになっていて、Tが返れば、それ以上展開しない、という指定が可能だった。
展開の指定をすれば、後でマーカーを置換するようなことはしなくても良い。
なお、大抵の処理系に付属のwalk-formはPortable CommonLoops(PCL)由来のものだが、この仕様は共通している。

(defun replace-fn (fsym replace form env) 
  (walk-form form
             env 
             (lambda (sub cxt env &aux stopp)
               (declare (ignore cxt env))
               (when (and (consp sub))
                 (when (eq fsym (car sub))
                   (setf (car sub) replace)
                   (setq stopp T))
                 (when (and (eq 'function (car sub))
                            (eq fsym (cadr sub)))
                   (setf (cadr sub) replace)
                   (setq stopp T))
                 (when (or (eq 'funcall (car sub))
                           (eq 'apply (car sub)))
                   (when (and (eq 'quote (caadr sub))
                              (eq fsym (cadadr sub)))
                     (setf (caadr sub) 'function)
                     (setf (cadadr sub) replace)
                     (setq stopp T))))
               (values sub stopp))))

上記では、lambdaで展開してしまったが、fletfibを定義し、funcall 'fibfuncall #'fibに書き換えても良いと思う。

(defsubstself fib (n)
  (declare (optimize (speed 3) (safety 0) (debug 0)))
  (declare (fixnum n))
  (the fixnum
       (if (< n 2)
           n
           (+ (funcall 'fib (1- n))
              (funcall 'fib (- n 2))))))
===>
(defun fib (n)
  ...
 (the fixnum
      (if (< n 2)
          n
          (+ (funcall #'(lambda (n)
                          (declare (optimize
                                    (speed 3)
                                    (safety 0)
                                    (debug 0)))
                          (declare (fixnum n))
                          (the fixnum
                               (if (< n 2)
                                   n
                                   (+ (funcall 'fib
                                               (1- n))
                                      (funcall 'fib
                                               (- n
                                                  2))))))
                      (1- n))
             (funcall #'(lambda (n)
                          (declare (optimize
                                    (speed 3)
                                    (safety 0)
                                    (debug 0)))
                          (declare (fixnum n))
                          (the fixnum
                               (if (< n 2)
                                   n
                                   (+ (funcall 'fib
                                               (1- n))
                                      (funcall 'fib
                                               (- n
                                                  2))))))
                      (- n 2)))))))

こうするとSBCLでも30%程の高速化となった。
ちなみに、Allegro CLだと普通に書いたものより遅くなるため、余計なことはしない方が良いらしい。

結び

HiLISPがfuncall 'fnをどう解釈するのかは分からないので、展開する場合としない場合を考えてみた。

Common Lispではインライン指定は処理系によって任意に解釈できることは知っていたが、調べてみると結構ばらばらなんだなと思った次第。
ちなみにSBCLでは、再帰的な局所関数ではlabels+inline指定をすると展開がかなり効く模様。
なお、大抵の処理系では、今回のような手作りの展開で速くなるが、Allegro CLの場合は、別の作法があるらしく、寧ろずっと遅くなるので注意。


HTML generated by 3bmd in LispWorks 7.0.0

cadadadadddrはなんと読んだらよいのか

Posted 2017-10-24 19:22:07 GMT

Common Lispでは carからcddddrまで30のcarcdrの組み合わせが備え付けで定義されているが、Common Lispプログラマは、caadarのようなものを一体どのように発音しているのだろうか。

このブログのドメイン名は、cddddr.orgだが、くだだだ・だーと日本語の語感として口にしやすいので選んだりしたのだが、dの連続は良いとしても、aの連続や、cdrcdarの違い等はどう表現したら良いのか良くく分からない。

RMSが講演でcaarを「か・あー」と発音していたのを観て、なるほど区切ってみたら案外区別が付くかもなと思い、Common Lispの備え付けに日本語的な発音を付けてみた。

区切りは、a→aとd→aに遷移する時に付けると区別がつきやすいように思う。
また、aが連続する場合は、一番目と二番目の間だけ区切りをはっきりさせると発音しやすい。

下記では区切りを促音にしているが、長音にしても良いだろう。

なお、カダーはキャダーという人もいるようだ(RMSもキャダーと発音していた)。
また、RG(グリーンブラット)は、CADRマシンをカダーと発音していた。同じMITでもまちまちらしい。

  • car: かー
  • cdr: くだー
  • caar: かっあー
  • cadr: かだー
  • cdar: かっだー
  • cddr: くだだー
  • caaar: かっああー
  • caadr: かっあだー
  • cadar: かだっあー
  • caddr: かだだー
  • cdaar: くだっああー
  • cdadr: くだっあだー
  • cddar: くだだっあー
  • cdddr: くだだだー
  • caaaar: かっあああー
  • caaadr: かっああだー
  • caadar: かっあだっあー
  • caaddr: かっあだだー
  • cadaar: かだっああー
  • cadadr: かだっあだー
  • caddar: かだだっあー
  • cadddr: かだだだー
  • cdaaar: くだっあああー
  • cdaadr: くだっああだー
  • cdadar: くだっあだっあー
  • cdaddr: くだっあだだー
  • cddaar: くだだっああー
  • cddadr: くだだっあだー
  • cdddar: くだだだっあー
  • cddddr: くだだだだー

この法則でいくと、本題名のcadadadadddrは、かだっあだっあだっあだだだーと読めることになる。 以上、おそまつ。


HTML generated by 3bmd in LispWorks 7.0.0

Lem使ってみた

Posted 2017-10-21 11:04:10 GMT

Common Lisp製のEmacs系エディタのlemがOpen Collectiveに参加したとのことで、自分も支援してみた。
自分はLispWorksを利用していて、折角LispWorksを購入したからには元を取ろうという貧乏くさい考えで、この二年位は殆どCommon Lispのコードは元より普段の職場での仕事でもLispWorksのエディタでテキストを編集している。

ということで、lemは使ったことがなかったのだが、折角なので使ってみた。

導入

とりあえず、GitHub: cxxxr: lemからソースを持ってきて、Quicklispがロードできる場所に配置。
自分は、Common Lisp処理系内部から使う派なので、あとは、(ql:quickload :lem)して、Common Lisp処理系をダンプするかすることにした。
ちなみに残念ながら現状LispWorks 7.0では上手く動かないらしい。後でちょっとみてみようかなと思う。

使ってみる

起動は、(lem:lem)。伝統のed関数から呼び出すようにしても良さそう。

自分的に必須コマンドである()を対で入力してくれるコマンド(make-())と、コッカからの移動コマンド(move-over-))がなかったので追加してみた。
lemの所作が良く分からないが、とりあえず動けば良いかなという感じ。
ちなみにこれらコマンドは1970年代のEmacsから存在している。

;; -*- lisp -*-
(ql:quickload :g000001.tools.tpd-blog)

(in-package :lem)

(define-key *global-keymap* "C-_" 'undo)

(deftype whitechar () '(member #\Space #\Tab #\Return #\Newline))

(define-command make-\(\) (n) ("p") (let ((cp (current-point))) (insert-character cp #\( n) (insert-character cp #\) n) (prev-char n)))

(define-key *global-keymap* "M-(" 'make-\(\)) (define-key *global-keymap* "M-L" 'make-\(\))

(defun backward-search-rper () (save-excursion (do* ((p (character-offset (current-point) -1)) (c (character-at p) (character-at p))) ((char= #\) c) p) (unless (typep c 'whitechar) (return nil)) (character-offset p -1))))

(defun backward-delete-to-rper () (save-excursion (do* ((p (character-offset (current-point) -1)) (c (character-at p) (character-at p))) ((char= #\) c) p) (unless (typep c 'whitechar) (return nil)) (delete-character p) (character-offset p -1))))

(define-command move-over-\) () () (let ((rper (backward-search-rper))) (if rper (progn (backward-delete-to-rper) (scan-lists (current-point) 1 1 T) (lem.language-mode:newline-and-indent 1)) (progn (scan-lists (current-point) 1 1 T) (lem.language-mode:newline-and-indent 1)))))

(define-key *global-keymap* "M-)" 'move-over-\)) (define-key *global-keymap* "M-:" 'move-over-\))

(define-command Process-Entries-And-Preview (p) ("p") (declare (ignore p)) (g000001.tools.tpd-blog:process-entries-and-preview (buffer-filename (current-buffer)) #P"/mc/"))

(define-command Publog (p) ("p") (declare (ignore p)) (g000001.tools.tpd-blog:publish-entries-and-preview (buffer-filename (current-buffer))))

(define-command Insert-UNIVERSAL-TIME (p) ("p") (declare (ignore p)) (let ((ut (get-universal-time))) (multiple-value-bind (s m h d mo y) (decode-universal-time ut) (declare (ignore s)) (insert-string (current-point) (format nil "~D ;~D-~2,'0D-~2,'0DT~2,'0D~2,'0D" ut y mo d h m)))))

;;; *EOF*

むすび

このブログはLispWorksのエディタから更新できるようにしていたが、何故か無駄に更新コマンドに可搬性を持たせて作成していたので、LispWorksのコマンドをちょっと変更するだけでlemからもこのブログを更新できるようになった。
ということで、この記事も記念にlemで書いてlem上から更新を実行してみた。

また、TwitterもLispWorksからしているが、これもちょっとしたコマンドの書き直しでlem上から簡単につぶやけるようになった。
基本的にエディタ側で作り込むのではなくCommon Lisp側で完結するツールを作成し、フロントエンドはほぼ呼び出すだけの構成にしておくとSLIME・lem・LispWorks等エディタで共通で使いまわせるので良いかもしれない。

lemは現在ターミナルで動くが、今後ブラウザ上や、Electron化も検討されているらしいので色々期待している。


HTML generated by 3bmd in SBCL 1.4.0

SAILDART Lispお宝発掘隊 (1)

Posted 2017-10-15 02:33:20 GMT

前回は、

  • [TCH,LSP]
  • [DWP,LSP]
  • [LAP,LSP]
  • [WD,LSP]

を眺めた。
今回も引き続き下から順番に確認していくことにしよう。

[STR,LSP]

Common Lispの国際標準化についての議論が行なわれていたメーリングリスト。
日本やヨーロッパ勢も参加し、ANSIからISOの流れに繋がっていく。

またこれもmhonarcでhtml化してみた。

[PLC,LSP]

日本の奥乃博先生が中心となって開催された、第三回LISPコンテストおよび第一回PROLOGコンテストのファイル
コンテストは各種ベンチマークの性能を比べる。
何故ここにあるのか謎だが、奥乃先生がSAILにいらしたのと関係があるのかもしれない。
ProLog Contestの略かと思われる。

参考

[LSC,LSP]

同上でこちらは、LiSp Contestのファイル

[MLI,LSP]

SAILで開発され使われていたALGOL記法のLISPであるMLISPのファイル。
処理系のファイルや、メールサーバーのソースコードっぽいものが点在。

参考

[WHT,LSP]

CLtL1のTeX原稿。TeXのマクロでBOLIO風の記述ができるようにしているらしい。
(BOLIOとはMIT Lispコミュニティで利用されていたマークアップ言語)

ファイルを眺めると、CLtL1はSpice Lispのマニュアルの原稿を上書きして作っていたことが分かる。

CLtL2はHTML化され今でも閲覧できるサイトもあり入手も可能だが、CLtL1はHTML化はされていないので案外貴重。
そのうちHTML化してみたい。

今回はここまで。


HTML generated by 3bmd in LispWorks 7.0.0

SAILDART Lispお宝発掘隊 (0)

Posted 2017-10-02 17:06:24 GMT

SAILとは、Stanford Artificial Intelligence Laboratoryの略でスタンフォードAIラボのこと。
DARTとはDump And Restore Techniqueの略だそうで、1972から1990位まで稼動していたバックアップシステムだそう。
そのアーカイブが10年位前から公開されているのが、SAILDART.ORG
アーカイブの中には、マッカーシー先生や、クヌース先生のホームディレクトリがあったりもするし(非公開)、TeXが生れたシステムでもあるので、TeX関係のファイルも多数ある。
Lisp関係では、Common Lispの仕様策定のメールでの議論は、ここSAILのシステムを中心としていたようで多数のお宝が眠っている(と私のようなマニアは考えている)

以前から発掘に勤しんでいるのだが、一度端から端まで確認してみようかなと思い、ブログに記録を残しことにしてみた次第。

まず、システムのLSPディレクトリがあり、このディレクトリは ざっとこんな感じ

  • [MAC,LSP]
  • [TIM,LSP]
  • [COM,LSP]
  • [CLS,LSP]
  • [206,LSP]
  • [NEW,LSP]
  • [FTL,LSP]
  • [MRS,LSP]
  • [AID,LSP]
  • [VLI,LSP]
  • [T,LSP]
  • [VIO,LSP]
  • [RUT,LSP]
  • [OLD,LSP]
  • [IL,LSP]
  • [LSP,LSP]
  • [SCH,LSP]
  • [CMP,LSP]
  • [BUG,LSP]
  • [LIB,LSP]
  • [QLA,LSP]
  • [X3,LSP]
  • [WHT,LSP]
  • [MLI,LSP]
  • [LSC,LSP]
  • [PLC,LSP]
  • [STR,LSP]
  • [TCH,LSP]
  • [DWP,LSP]
  • [LAP,LSP]
  • [WD,LSP]

ディレクトリの名前からだと、中身がなんだかさっぱり見当がつかないが、とりあえず下から全部確認していくことにしよう。

[WD,LSP]

READ.MEに

THIS DIRECTORY CONTAINS EXPERIMENTAL LISP SYSTEM PROGRAMS WHICH SHOULD
NOT BE DEPENDEN UPON

等々とあるので、WDとは、Working Directoryの略かなんかだろうか。

特に見るべきものもないが、pretty.rutのファイルは、UCI LISPのプリティプリンタの定義かなんかだろうか。

[LAP,LSP]

ここも良く分からないが、UCI LISPっぽい。dfuncというのは、UCI LISPのdefunみたいなもだったと思う。
Scheme等と同じく(dfunc (foo arg)...)という形式なのが面白い。
1973年のファイルなので、この定義形式はSchemeに先行するだろう。

[DWP,LSP]

READ.MEに

All files here are on the LISP UDP as of 5/7/80.
        -rpg-

とあるがまったく意味が不明

MLISPの処理系のソースっぽいものがMIDASで書かれている断片がある。

[TCH,LSP]

Common Lispの仕様策定のグループで標準化について技術的な議論をするメーリングリストのアーカイブが置かれていた場所らしい。 メーリングリストの名前は、cl-technical TCHとはtechnicalということか。

とりあえずmhonarcでまとめてみた。

とりあえず、満足したので今日はここまで。


HTML generated by 3bmd in LispWorks 7.0.0

廃止になった expand-defclass について

Posted 2017-10-01 16:57:10 GMT

defclassで構造体を定義するのってできなかったっけかなあと思い、ちょっと調べてみたが、どうもstructure-classとの統合は未完に終わっているようで、MOPでも構造体の生成についての記述はない様子。

(make-instance (make-instance 'structure-class))

に類することができれば、どうにかなりそうだが、思えば構造体にそのような道具はなく、実行時に定義するならコードを生成してからevalみたいなことになりそうだ。
ちなみに、evalが絡むと大抵

(let ((x 42))
  (defstruct foo
    (x x)
    y
    z))

(make-foo) → #S(foo :x 42 :y nil :z nil)

みたいなことができなくなるので避けたい所。
(但し構造体については上記ができても殆ど意味がない)

構造体の定義については、defstructを書くしかないという結論になったが、それでは、defclassdefstructに展開する方向で考えてみようということで、これに使えそうな、expand-defclassを思い出したので使ってみることにした。

(defmethod clos::expand-defclass ((proto structure-class) 
                                  (metaclass (eql 'structure-class))
                                  name
                                  supers
                                  slots
                                  class-options)
  `(defstruct (,name (:include ,@supers))
     ,@(mapcar (lambda (s) 
                 (if (consp s)
                     `(,(car s) ,(getf (cdr s) :initform))
                     `(,s nil)))
               slots)))

これを使うと下記のように書ける

(defstruct foo 
  x
  y
  z)

(defclass bar (foo) ((a :initform 42) b c) (:metaclass structure-class))

(make-bar) → #S(bar :x nil :y nil :z nil :a 42 :b nil :c nil)

(defclass bar (foo)
  ((a :initform 42)
   b
   c)
  (:metaclass structure-class))

をマクロ展開すると、

(defstruct (bar (:include foo)) (a 42) (b nil) (c nil))

となる。

expand-defclassについて

expand-defclassは、MOPの初期(1987年頃)には存在していたが、定義構文の展開方法については具体的に規定しないことになり、定義構文のマクロ展開メソッドは諸々1988年には消えてしまった。
そのためAMOPには載っていない。

(expand-defclass prototype-instance name superclasses slots options environment)

という総称関数だが、展開がメソッドでカスタマイズできるというのが、なかなか良さそう。
なお、LispWorks版は、environment 引数は取らないが、環境は取り込んでいるので謎なことをしている様子。

なお、初期MOPの仕様にほぼ沿っているexpand-defclassは、調べた限りでは、どうもLispWorksにしか存在しないようだ。
(TI ExplorerのTICLOSにはFlavorsで定義されているexpand-defclassがあったのと、Portable CommonLoopsに仕様が違う同名の関数があった。)

まとめ

Common LispもDylan位に色々統合されてくれていたら良かったのになと思ったりする。
ちなみに、Dylanでは動的さが制御できるので性能を出したい場合は制限をかけるようになっている。

また、Eclipse Common Lispは全面的にOOPで書かれているが、Dylanのようにsealed指定が可能なように拡張されていたようだ。


HTML generated by 3bmd in LispWorks 7.0.0

SETFのFって結局なんなの

Posted 2017-10-01 14:34:54 GMT

今日comp.lang.lispの過去ログを眺めていたら、SETFのFはFunctionのF説を説明している人をみつけた。

The thread suggests both FORM and FIELD, but the original meaning was
FUNCTION :-) When the construct was first suggested it was called
SETFQ for "set with function quoted, everything else evaluated", and
only later abbreviated to SETF. (Source: the Gabriel/Steele "Evolution
of Lisp" paper in HOPL-II).

自分もMITのLispマシングループが元ネタとしたA LISP machine with very compact programsの論文には、FieldやFormという記載はなく、Fといえば、Function位だよなあと思っていたが、Evolution of Lispにも記載があるとのことなので確認してみたら、

Deutsch commented that the special form used here is called SETFQ because 
"it quotes the function and evaluates everything else." 
This name was abbreviated to SETF in Lisp-Machine Lisp. 
Deutsch attributed the idea of dual functions to Alan Kay

と書いてあった。
また、Deutsch氏の論文にも、

A more useful function is (SETFQ (fn arg1 ... argn) newvalue)
which quotes the function name and evaluates everything else. 
This allows RPLACA, for example, to be defined as (LAMBDA (X Y) (SETFQ (CAR X) Y) ).

とあり完全に見逃していたらしい。
この論文でいうFunctionは、CLでいうsetfのaccessorみたいな所。

ちなみに、他の説も挙げておこう。

FはForm説

これはsetfはシンボルだけではなくフォームを取るからなんだろうけど、特に誰かの裏付けはなく、なんとなくの皆の印象という感じだろうか。

FはField説

色々な所を眺めると、Kent Pitman氏が広めた感じだが、Pitman氏もDavid Moon氏から教えてもらったようなので大元はMoon氏らしい。
LISP 原書第3版(I) 13章 構造体 にも同様の説明がある。

ちなみに、Deutsch氏のsetfqの機構はAlan Kay氏のアイデアが源泉とあるが、Lisp界全体でこのアイデアが最初に登場したのは、更に10年程遡って1964年頃のLISP 2だったようだ。
なお、LISP 2はAlgolからヒントを得ている。
LISP 2のS式構文では、

(set (car x) 42)

のように書ける。

まとめ

setfのFはFunctionのFだった。と書けば一行で終わる内容だが、色々書くとこんなに長くなる。

関連リンク


HTML generated by 3bmd in LispWorks 7.0.0

ISLISPにモジュールシステムがないのは何故か

Posted 2017-09-03 23:29:18 GMT

ISLISPではCommon Lispのようなパッケージがなくても大丈夫、という話を耳にした。
プログラムの規模によっては大丈夫なのかもしれないが、ISLISPにもパッケージを導入した実装もあることだし、実際どうなのか調べてみた。

ちなみに、パッケージが付いたISLISPであるTISLのサイトは消滅しているので下記にarchive.orgのリンクを貼っておく

また、TISLのパッケージシステムについては下記の論文が一番詳しいようだ。

ISLISPとモジュールシステム

とりあえず、調べて分かったことをまとめてしまうと

  • ISLISPは産業利用が期待されており、モジュールシステムは必須と策定委員会も考えていた。
  • 必須と考えてはいたものの、まとめ切れなかったので見送りとした。

ということらしい。

LISP言語国際標準化と日本の貢献 にはISLISPの成立までの流れが詳細に書いてあるが、Common Lispのパッケージシステムが採用されなかった理由としては、

  • Common Lispのパッケージはデザインが古い

    • パッケージ間の名前の共有関係の静的チェックが困難
    • 名前の隠蔽をすることでの抽象化が上手く実現できない

あたりが述べられている。

Common Lispのパッケージシステムのデザインが良くないので、ISLISPに取り込まれず、ISLISPは将来の課題としたが、宙に浮いてしまった、というところだろうか。

ISLISPの話に、突然Common Lispが出てくるのに違和感があるかもしれないが、こちらもLISP言語国際標準化と日本の貢献を読めばISLISPがCommon Lispのサブセット的な面が強いことが分かると思う。
そもそもCommon Lispが成功したので、ANSIやISOで標準化しようという話になったが、ISO Common Lispが色々あって失敗し、ISLISPに至るという話でもある。

まとめ

ANSI Common Lispにしろ、ISLISPにしろ、時期尚早として見送ったものの、色々あってその時期は永遠にやってこなさそう、という話は多い気がする。


HTML generated by 3bmd in LispWorks 7.0.0

マクロ文字で絵文字を使おう

Posted 2017-08-22 16:34:42 GMT

Perl6は以前から演算子などにUnicodeにASCII外の文字を積極的に使っているが、今回アトミックな操作の演算子として⚛がはいったらしい。

こんな感じのコードらしいが、環境によっては絵文字になる様子。

my atomicint $i = 0;
start { $i ⚛= 1 }
while ⚛$i == 0 { }

ということで、マクロ文字でこういう拡張が簡単にできるCommon Lispでも早速真似してみたい。

(flet ((|read-🤙| (srm chr)
         (declare (ignore chr))
         (cl:quote cl:funcall))
       (|read-λ| (srm chr)
         (declare (ignore chr))
         (cl:quote cl:lambda)))
  (set-macro-character #\🤙 #'|read-🤙|)
  (set-macro-character #\λ #'|read-λ|))

(🤙(🤙(λ (f) ((λ (proc) (🤙f (λ (arg) (🤙(🤙proc proc) arg)))) (λ (proc) (🤙f (λ (arg) (🤙(🤙proc proc) arg)))))) (λ (f) (λ (n) (if (< n 2) n (+ (🤙f (1- n)) (🤙f (- n 2))))))) 10) → 55

Unicode 9.0 に Call Me Hand “🤙” (U+1F919) という丁度良いのがあったので使ってみた。

🤙 が開き括弧っぽいので、もう一捻りして、

(flet ((|read-🤙| (srm chr)
         (declare (ignore chr))
         (cons (quote funcall)
               (read-delimited-list #\) srm T)))
       (|read-λ| (srm chr)
         (declare (ignore chr))
         (cl:quote cl:lambda)))
  (set-macro-character #\🤙 #'|read-🤙|)
  (set-macro-character #\λ #'|read-λ|))

🤙🤙(λ (f) ((λ (proc) 🤙f (λ (arg) 🤙🤙proc proc) arg)))) (λ (proc) 🤙f (λ (arg) 🤙🤙proc proc) arg)))))) (λ (f) (λ (n) (if (< n 2) n (+ 🤙f (1- n)) 🤙f (- n 2))))))) 10) → 55

こんなのもどうだろうか。これならLisp-1と文字数は一緒になる。
とはいえ、ここまで来るとエディタにも追加設定して追従させないといけないが。 (Emacsなら括弧の文字設定が可能なので多分大丈夫だろう)


HTML generated by 3bmd in LispWorks 7.0.0

Common Lispを実装するのに再利用できそうなコンポーネント群

Posted 2017-08-21 16:29:38 GMT

たった一人でCommon Lispの処理系を作るというのはあまり耳にしないけれど、そんな人も皆無ではない。
たとえば、XCLや、Eclips CL
どちらももう開発は停止しているが、Eclips CLは、ANSI CL以降に実装されただけあって、処理自体がANSI CL以降のスタイルでOOP機能を使って書かれたりもしている。そしてMOP付き。

一人でMOP装備の処理系を開発しているというのは極端だけれど、チームで開発している人達も、大抵、既存のコンポーネントは再利用していることが殆ど。
Common Lispの処理系のコンポーネントの配布もまたかなり昔からある。

一番古い所では、Spice Projectが配布していたもので、1981・2年から存在する。
京大で独自に実装されたためKCLがこのキットを使っていなかったことに当時のCommon Lisp実装者達が驚いたのは有名(でもない)
ちなみに、Spice Projectのキットを使っていたものには、TOPS-20 Common Lispや商用のVAX LISPなども存在する。

また、オブジェクト指向システムでは、Portable CommonLoops(PCL)が参照実装として流通していて、なんだかんだで現在開発が活発なCommon Lisp処理系はどれもこれをベースにしている。

LOOPの実装は大抵の処理系は、MIT LOOPのバージョン829を元にしている。
MIT LOOPが大半なのでMIT LOOP依存なコードが発生してしまっているほど。

Common Lispからブートストラップする処理系としては、上述したHoward Stearns氏のEclisp CL、Robert Strandh氏のSICL、峯島氏のSacraがあり、Eclipse CLは商用処理系として販売もされていた。
SICLは規格に準拠したより綺麗な実装を目指していて、当該プロジェクト以外でもClaps等で、コンポーネントが利用されている。
Sacraは、XCLで部分的に利用されたりしている。

その他Thinlisp等トランスレータ系のソースもあり、さらにマニアックな所では、古いLispマシンのソースや、Lucid CLのソースも入手可能。
とはいえこの辺りはライセンスが微妙。(一応CADRは、MIT Licenseだが)

また、CLISPが結構独自実装なのでコードの再利用性は高いかもしれない。

以上、なんのまとまりもないがリンクを並べておく


HTML generated by 3bmd in LispWorks 7.0.0

CrayでLisp

Posted 2017-08-06 08:23:13 GMT

Cray J90の公開エミュレータサイト現わる

先日、伝説のスパコンで有名なCray社のマシンであるCray J90のエミュレータを動かして公開している酔狂なサイトが登場した。

Cray J90は、Y-MPのエントリーレベルのマシンであるY-MP ELの後継機で1994年に登場したマシンらしい

Cray J90 と Lisp

珍しいマシン環境を見付けたらまずLispが動くかどうかを調べたいところ。
ざっと検索してみたところ、Portable Standard Lisp(PSL)がCray上で稼動していたようだ。
PSLとは、数式処理システムのREDUCEを動かすことを念頭に置いて開発されたStandard Lispの後継で、より可搬性の高い処理系。

Portable Standard Lisp を Cray J90 でビルドする

PSLについても最近の環境でビルドできるようにして公開している方がいるので、こちらのソースを利用することにしてみる(とはいえターゲットは1994年の環境だが……)

ちなみに、公開J90マシンへのファイルの転送はftpを利用するが、モードをpassiveにしないと上手く行かなかった(なんとなく懐かしい)

早速、ファイルを転送して展開する。
なお、当該機にgzipはないようなので注意。

makefileがgcc用なので、適当に修正する。

インタプリタのビルドは、make lispで、make lispcでコンパイラのビルドとなるが、インタプリタ環境ファイルとコンパイラ環境ファイルがごっちゃになるので、別々にした方が良いだろう。

とりあえず、make lispしてできたlispを環境が混ざらないように、別のディレクトリを作成し、そこに移動。そして実行してみる。

(de fib (n)
    (cond ((lessp n 2) n)
          (t (plus (fib (difference n 1))
                   (fib (difference n 2))))))

こんな感じのfibの定義を作成してloadさせてみる。

bash-2.03$ ./lisp
No initialization file: LISP-INI or LISP-INI
    S  T  D     L  I  S  P      [7.2] June 2015 
> (load "fib.lsp")
nil
> fib
> !$eof!$
> (fib 10)
55
> 

とりあえず動いた。

なお、コンパイラの方もlispcはできて実行できるのだが、どうも上手く動かせていない。

Common Lisp処理系は動くか

1994年の環境なので、KCL位なら動くかもしれない。
ちなみに、Franzの社史によると、Duane Rettig 氏が Allegro CLをX-MPに移植したとのこと。
Unicosは互換性が高いのでX-MP用のバイナリであれば動きそう。

まとめ

やはりCrayのマシンはロマン。
興味のある方は是非UnicosでCommon Lisp処理系が動くようにして頂きたい。


HTML generated by 3bmd in LispWorks 7.0.0

GambolでZebraベンチ

Posted 2017-06-28 13:06:26 GMT

Gambolとは、FROLICというCommon Lisp実装のS式Prologを拡張したものからProlog部分を抜き出したものらしい。

導入は、Quicklisp経由で、

(ql:quickload :gambol)

とすれば導入できる。

とりあえず、PrologのCommon Lisp実装を見付けたらZebraパズルでベンチをとってみることにしているので、早速いつもの組み合わせでベンチをとってみることにした。
(Allegro CL 8.2 64bit / Intel(R) Xeon(R) CPU E3-1230 v3 @ 3.30GHz)

なお、gambolだと色々試しているうちに定義が混ざるので定義の度にリセットするようなマクロを書いた。
また、gambolでは、匿名変数は??の筈だが、??だとどうも上手く動かないので、#?というリーダーマクロで一意なシンボルを生成した。

(cl:in-package :cl-user)

(eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickload :gambol))

(defpackage :gambol-zebra (:use :cl :gambol))

(in-package :gambol-zebra)

(eval-when (:execute :compile-toplevel :load-toplevel) (defvar *gambol-zebra-readtable* (copy-readtable nil)) (set-dispatch-macro-character #\# #\? (lambda (s c a) (declare (ignore s c a)) (gensym "?")) *gambol-zebra-readtable*) (setq *readtable* *gambol-zebra-readtable*))

(defmacro define-predicate (name &body clauses) `(progn (clear-rules '(,name)) ,@(mapcar (lambda (c) `(*- ,@c)) clauses)))

(define-predicate member ((member ?item (?item . #?))) ((member ?item (#? . ?rest)) (member ?item ?rest)))

(define-predicate nextto ((nextto ?x ?y ?list) (iright ?x ?y ?list)) ((nextto ?x ?y ?list) (iright ?y ?x ?list)))

(define-predicate iright ((iright ?left ?right (?left ?right . ?rest))) ((iright ?left ?right (?x . ?rest)) (iright ?left ?right ?rest)))

(define-predicate zebra ((zebra ?h ?w ?z) ;; Each house is of the form: ;; (house nationality pet cigarette drink house-color) (= ?h ((house norwegian #? #? #? #?) ;1,10 #? (house #? #? #? milk #?) #? #?)) ; 9 (member (house englishman #? #? #? red) ?h) ; 2 (member (house spaniard dog #? #? #?) ?h) ; 3 (member (house #? #? #? coffee green) ?h) ; 4 (member (house ukrainian #? #? tea #?) ?h) ; 5 (iright (house #? #? #? #? ivory) ; 6 (house #? #? #? #? green) ?h) (member (house #? snails winston #? #?) ?h) ; 7 (member (house #? #? kools #? yellow) ?h) ; 8 (nextto (house #? #? chesterfield #? #?) ;11 (house #? fox #? #? #?) ?h) (nextto (house #? #? kools #? #?) ;12 (house #? horse #? #? #?) ?h) (member (house #? #? luckystrike oj #?) ?h) ;13 (member (house japanese #? parliaments #? #?) ?h) ;14 (nextto (house norwegian #? #? #? #?) ;15 (house #? #? #? #? blue) ?h) (member (house ?w #? #? water #?) ?h) ;Q1 (member (house ?z zebra #? #? #?) ?h) ;Q2 ))

(defun zebra-benchmark (&optional (n 1000)) (declare (optimize (speed 3) (safety 0))) (let (rt0 rt1) (time (loop :initially (setf rt0 (get-internal-run-time)) :repeat n :do (pl-solve-one '((zebra ?h ?w ?z))) :finally (setf rt1 (get-internal-run-time)))) (multiple-value-call #'values (/ (* n 12825) (/ (- rt1 rt0) 1000.0)) (values-list (pl-solve-one '((zebra ?h ?w ?z)))))))

結果

結果は、82秒とPAIPrologの8倍遅い結果となった。
SBCLだと18秒、LispWorksだと30秒程度なので、Allegro CLと相性が良くないのかもしれない。
また、Allegro CLとLispWorksでは、スタックが溢れるので、

#+allegro (sys:set-stack-cushion nil)
#+lispworks (setq sys:*stack-overflow-behaviour* nil)

等と処置する必要があるかもしれない。

(gambol-zebra::zebra-benchmark 1000)
; cpu time (non-gc) 44.780000 sec user, 0.000000 sec system
; cpu time (gc)     38.100000 sec user, 0.000000 sec system
; cpu time (total)  82.880000 sec (00:01:22.880000) user, 0.000000 sec system
; real time  82.874676 sec (00:01:22.874676)
; space allocation:
;  263,790,840 cons cells, 9,367,684,480 other bytes, 0 static bytes
154741.8
→ (?h
   (house norwegian fox kools water yellow)
   (house ukrainian horse chesterfield tea blue)
   (house englishman snails winston milk red)
   (house spaniard dog luckystrike oj ivory)
   (house japanese zebra parliaments coffee green)) 
  (?w . norwegian) 
  (?z . japanese) 

結び

PrologのCommon Lisp実装は他にも結構あるらしいので、見付けたらZebraパズルを試していきたい。


HTML generated by 3bmd in LispWorks 7.0.0

Common LispのScreamerでZebraベンチ

Posted 2017-06-13 16:22:44 GMT

Screamerとは古くからあるCommon Lispの非決定性計算のライブラリで、知る人ぞ知るという感じのものだが、現在もQuicklisp経由で簡単に導入することが可能。

(ql:quickloda :screamer)

数年前ScreamerでZebraパズルを記述したものがあったので試してみたが、どうも遅いっぽいなあという漠然とした印象だけ残っていた。
最近Zebraパズルばっかりやっているが、無駄な知見が溜りつつある丁度良いタイミングなので果して本当に遅かったのか確認してみることにした(暇ともいう)

筆者が以前目にしたものは、SBCLの開発で有名な、Nikodemus Siivola氏が書いたものだったらしい。

こちらのコードを少しSWI-Prologのコードっぽくして、計時してみた。

(declaim (optimize (speed 3) (safety 0) (debug 0) (compilation-speed 0)))

(eval-when (:compile-toplevel :load-toplevel :execute) (ql:quickloda :screamer))

(in-package :s)

(defmacro let-integers-betweenv (((min max) var-list) body) `(let ,(loop for i in var-list collect (list i `(an-integer-betweenv ,min ,max))) ,body))

(defun all-differentv (list) ;; Functionally the same as (apply #'/=v list), but faster. (labels ((all-different (x xs) (if (null xs) t (andv (notv (=v x (car xs))) (all-different x (cdr xs)) (all-different (car xs) (cdr xs)))))) (all-different (car list) (cdr list))))

(defun nextto (x y) (let ((d (an-integer-betweenv -1 1))) (assert! (/=v d 0)) (assert! (=v d (-v x y)))))

(defun iright (x y) (assert! (=v x (+v y 1))))

(defun zebra-problem () (let-integers-betweenv ((1 5) (english spaniard japanese ukrainian norwegian red green ivory yellow blue dog snails fox horse zebra kools chesterfield winston luckystrike parliament tea coffee milk oj water z w)) (let ((nationality (list english spaniard japanese ukrainian norwegian)) (color (list red green ivory yellow blue)) (smoke (list kools chesterfield winston luckystrike parliament)) (pet (list zebra dog snails fox horse)) (drink (list water tea coffee milk oj))) (assert! (all-differentv nationality)) (assert! (all-differentv color)) (assert! (all-differentv smoke)) (assert! (all-differentv pet)) (assert! (all-differentv drink)) ;; (assert! (=v norwegian 1)) (assert! (=v milk 3)) (assert! (=v english red)) (assert! (=v spaniard dog)) (iright green ivory) (nextto norwegian blue) (assert! (=v kools yellow)) (assert! (=v green coffee)) (assert! (=v ukrainian tea)) (assert! (=v luckystrike oj)) (assert! (=v japanese parliament)) (assert! (=v winston snails)) (assert! (=v z zebra)) (assert! (=v w water)) (nextto horse kools) (nextto fox chesterfield)

(destructuring-bind (z w &rest result) (one-value (solution (list z w nationality pet drink color smoke) (static-ordering #'linear-force))) (let* ((syms '((english spaniard japanese ukrainian norwegian) (zebra dog snails fox horse) (water tea coffee milk oj) (red green ivory yellow blue) (kools chesterfield winston luckystrike parliament) )) (result (apply #'mapcar #'list (mapcar (lambda (x) (mapcar #'second (sort x #'< :key #'first))) (mapcar (lambda (x y) (mapcar #'list x y)) result syms))))) (list (nth 0 (nth (1- z) result)) (nth 0 (nth (1- w) result)) result))))))

(zebra-problem)(japanese norwegian ((norwegian fox water yellow kools) (ukrainian horse tea blue chesterfield) (english snails milk red winston) (spaniard dog oj ivory luckystrike) (japanese zebra coffee green parliament)))

Allegro CL 8.2 64bitで大体210秒位。オリジナルも大体同じ位のスピードで、assert!の節を関数として括り出しても性能的には問題ないようだ。
ちなみに、LispWorksや、SBCLだと130秒位で終了するので、コードの最適化がSBCLやLispWorksに向けて施されているかもしれない。

;(time (dotimes (i 1000) (zebra-problem)))
; cpu time (non-gc) 197.770000 sec (00:03:17.770000) user, 0.010000 sec system
; cpu time (gc)     12.670000 sec user, 0.000000 sec system
; cpu time (total)  210.440000 sec (00:03:30.440000) user, 0.010000 sec system
; real time  210.561596 sec (00:03:30.561596)
; space allocation:
;  74,892,808 cons cells, 70,951,714,176 other bytes, 0 static bytes)

コードの中身としては、これまでのリスト処理のコードとはちょっと違っていて、各要素に1から5までの数値を割り当て、要求された条件に合う解を見付けてくるようなものになっている。
Prologに比べると変数の初期化等のコード量が多く、その辺りの見通しが少し良くない。

なおオリジナルのコードのコメントによると、もう少し速くなる書き方があるらしい。
20倍位速くなればPAIProlog並ということになるが、ここまで手続的に書いてPAIPrologより遅いとなるとZebraのような問題に限っては、組み込みPrologを使った方が楽で良いなと思ってしまう。

結び

いかつい名前から勝手に超高速なものという印象を持っていたが、Zebraに関しては若干期待外れだった。

Screamerが得意とする問題もあると思うので、得意なものもそのうち確認してみたい。


HTML generated by 3bmd in LispWorks 7.0.0

Common LispのminiKANRENでZebraベンチ

Posted 2017-06-09 19:09:28 GMT

Common Lisp上から使える論理型言語・DSLを適当に眺めたりしているが、今回はminiKANRENを試してみる。

miniKANRENとは

KANRENはScheme上に実装された論理型・関係型言語で、名前は日本語の関連(relation)に由来するらしい。
元々miniKANRENはそのサブセットだったようだが、今では一つの流派を成しているようだ。

実装がシンプルなので多数の言語の上で稼動する。最近のもので比較的有名なものとしてClojureのcore.logicがある。

Prologと比較すると、より関数型言語との親和性が高かったり、Occur Checkがあったり、基本的にcutはなかったり色々と違うようだ。

Zebraベンチを走らせてみる

Common LispにもminiKANRENは移植されていて、quicklisp経由で導入することができる。

(ql:quickload :kanren-trs)

とりあえず、毎度試しているSWI-Prolog版のZebraベンチのコードをminiKANRENで書いてみた。

(defpackage :k
  (:use :cl :kanren-trs))

(defun memb (item list) (fresh (a d) (conde ((== '() list) +fail+) ((== (cons item d) list)) ((== (cons a d) list) (memb item d)))))

(defun nextto (x y list) (conde ((iright x y list)) ((iright y x list))))

(defun iright (left right list) (fresh (a d r) (conde ((== '() list) +fail+) ((== (cons a '()) list) +fail+) ((== (cons left d) list) (== (cons right r) d)); left d:(right r) ((== (cons a d) list) (iright left right d)))))

(defun replace-_ (tree) (let ((vars '())) (labels ((frob (tree) (typecase tree (null '()) (atom tree) (cons (case (car tree) (_ (let ((s (gensym))) (push s vars) (cons s (frob (cdr tree))))) (otherwise (cons (frob (car tree)) (frob (cdr tree))))))))) (values (frob tree) vars))))

(defmacro fresh* (&body body) (multiple-value-bind (newbody vars) (replace-_ body) `(fresh (,@vars) ,@newbody)))

(defun zebra (h w z) (fresh* (== h `((norwegian ,_ ,_ ,_ ,_) (,_ ,_ ,_ ,_ ,_) (,_ ,_ ,_ milk ,_) (,_ ,_ ,_ ,_ ,_) (,_ ,_ ,_ ,_ ,_))) (memb `(englishman ,_ ,_ ,_ red) h) (memb `(spaniard dog ,_ ,_ ,_) h) (memb `(,_ ,_ ,_ coffee green) h) (memb `(ukrainian ,_ ,_ tea ,_) h) (iright `(,_ ,_ ,_ ,_ ivory) `(,_ ,_ ,_ ,_ green) h) (memb `(,_ snails winston ,_ ,_) h) (memb `(,_ ,_ kools ,_ yellow) h) (nextto `(,_ ,_ chesterfield ,_ ,_) `(,_ fox ,_ ,_ ,_) h) (nextto `(,_ ,_ kools ,_ ,_) `(,_ horse ,_ ,_ ,_) h) (memb `(,_ ,_ luckystrike oj ,_) h) (memb `(japanese ,_ parliaments ,_ ,_) h) (nextto `(norwegian ,_ ,_ ,_ ,_) `(,_ ,_ ,_ ,_ blue) h) (memb `(,w ,_ ,_ water ,_) h) (memb `(,z zebra ,_ ,_ ,_) h)))

KANRENでは匿名関数がサポートされているように見えるが、miniKANRENではサポートされていないらしい。
毎度freshで手書きで指定するのはあまりにも辛いのでfresh*という適当なマクロを書いてみた。なお入れ子での利用は想定していない。

ちなみにマクロで圧縮しないと、下記のようになる。Zebraのように変数が多いとちょっと辛い。

(defun zebra* (h w z)
  (declare (optimize (speed 3) (safety 0) (debug 0)))
  (fresh (a1 a2 a3 a4 a5
          b1 b2 b3 b4 b5
          c1 c2 c3 c4 c5
          d1 d2 d3 d4 d5
          e1 e2 e3 e4 e5)
    (== h `((norwegian ,a2 ,a3 ,a4 ,a5)
            (,b1 ,b2 ,b3 ,b4 ,b5)
            (,c1 ,c2 ,c3 milk ,c5)
            (,d1 ,d2 ,d3 ,d4 ,d5)
            (,e1 ,e2 ,e3 ,e4 ,e5)))
    (fresh (t1 t2 t3)
      (memb `(englishman ,t1 ,t2 ,t3 red) h))
    (fresh (t1 t2 t3)
      (memb `(spaniard dog ,t1 ,t2 ,t3) h))
    (fresh (t1 t2 t3)
      (memb `(,t1 ,t2 ,t3 coffee green) h))
    (fresh (t1 t2 t3)
      (memb `(ukrainian ,t1 ,t2 tea ,t3) h))
    (fresh (t1 t2 t3 t4 t5 t6 t7 t8)
      (iright `(,t1 ,t2 ,t3 ,t4 ivory) `(,t5 ,t6 ,t7 ,t8 green) h))
    (fresh (t1 t2 t3)
      (memb `(,t1 snails winston ,t2 ,t3) h))
    (fresh (t1 t2 t3)
      (memb `(,t1 ,t2 kools ,t3 yellow) h))
    (fresh (t1 t2 t3 t4 t5 t6 t7 t8)
      (nextto `(,t5 ,t6 chesterfield ,t7 ,t8) 
              `(,t1 fox ,t2 ,t3 ,t4) h))
    (fresh (t1 t2 t3 t4 t5 t6 t7 t8)
      (nextto `(,t5 ,t6 kools ,t7 ,t8) 
              `(,t1 horse ,t2 ,t3 ,t4) h))
    (fresh (t1 t2 t3)
      (memb `(,t1 ,t2 luckystrike oj ,t3) h))
    (fresh (t1 t2 t3)
      (memb `(japanese ,t1 parliaments ,t2 ,t3) h))
    (fresh (t1 t2 t3 t4 t5 t6 t7 t8)
      (nextto `(norwegian ,t1 ,t2 ,t3 ,t4)
              `(,t5 ,t6 ,t7 ,t8 blue) h))
    (fresh (t1 t2 t3 t4)
      (memb `(,w ,t2 ,t3 water ,t4) h))
    (fresh (t2 t3 t4)
      (memb `(,z zebra ,t2 ,t3 ,t4) h))))

計時

さて、これで、下記のような感じで、いつもと同じAllegro CL 8.2 64bitで1000回繰り返してみた。

(time 
 (dotimes (i 1000)
   (run nil (a)
     (fresh (h w z)
       (zebra h w z)
       (== a (list h w z))))))

; cpu time (non-gc) 216.919057 sec (00:03:36.919057) user, 0.000000 sec system ; cpu time (gc) 23.884974 sec user, 0.000000 sec system ; cpu time (total) 240.804031 sec (00:04:00.804031) user, 0.000000 sec system ; real time 240.977189 sec (00:04:00.977189) ; space allocation: ; 383,627,240 cons cells, 38,243,671,232 other bytes, 0 static bytes

結果は、216秒とかなり遅かった。PAIPrologの約20倍、AZ-Prolog・Allegro Prologと比較すると約250〜300倍遅い。
Common Lispの実装は特に高速化は施されていないのでこんなものなのかもしれない。

ちなみに今回、ウェブ上で散見されるZebraベンチにも色々なバージョンがあることと、述語の並べ方によって10倍以上の速度の違いが生じることがあることに気付いた。
SWI-Prologのベンチでは、家の情報(全体の情報)、水を飲んでいる物、シマウマの所有者の3つの変数を使うが、全体の情報を取得のみの場合もあるようだ。
また、述語の並べ方としては具体的には、

(defun zebra/ (h w z)
  (fresh*
    (== h `((norwegian ,_ ,_ ,_ ,_)
            (,_ ,_ ,_ ,_ ,_)
            (,_ ,_ ,_ milk ,_)
            (,_ ,_ ,_ ,_ ,_)
            (,_ ,_ ,_ ,_ ,_)))
    (iright `(,_ ,_ ,_ ,_ ivory)
            `(,_ ,_ ,_ ,_ green) h)
    (nextto `(norwegian ,_ ,_ ,_ ,_)
            `(,_ ,_ ,_ ,_ blue) h)
    (memb `(englishman ,_ ,_ ,_ red) h)
    (memb `(spaniard dog ,_ ,_ ,_) h)
    (memb `(japanese ,_ parliaments ,_ ,_) h)    
    (memb `(ukrainian ,_ ,_ tea ,_) h)
    (nextto `(,_ ,_ chesterfield ,_ ,_)
            `(,_ fox ,_ ,_ ,_) h)
    (memb `(,_ snails winston ,_ ,_) h)    
    (memb `(,_ ,_ kools ,_ yellow) h)    
    (memb `(,_ ,_ luckystrike oj ,_) h)
    (memb `(,w ,_ ,_ water ,_) h)
    (memb `(,z zebra ,_ ,_ ,_) h)
    (memb `(,_ ,_ ,_ coffee green) h)
    (nextto `(,_ ,_ kools ,_ ,_) 
            `(,_ horse ,_ ,_ ,_) h)))

のような順番で述語を記述すると、約11秒なので20倍程度は速い。
なお、Allegro Prologなどでもこの述語の並びだと10倍程度速くなるようなので、Zebraベンチの場合は述語の並びを揃えないと、うまく比較できないと考えた方が良いようだ。

結び

miniKANRENは親言語とのデータのやりとりも簡単で使い勝手良さそうだ。
miniKANRENといえば、The Reasoned Schemerらしいので、そのうち読んでみたい。


HTML generated by 3bmd in LispWorks 7.0.0

Boizumault本のMini-Prolog-IIでZebraベンチ

Posted 2017-06-02 15:16:36 GMT

先日退職し、またも無職となったが、同僚から餞別でPatrice Boizumault氏のThe Implementation of Prologを頂いた。
これ前から欲しかったので非常に嬉しい!。ありがたや!!。

この本は、タイトル通りPrologを実装していこうという本で、出版は1993年と古いが、WAMベースなPrologをCommon Lispで実装しようというのが、筆者的には魅力の本。

この本のコードはCMUのAIリポジトリにあるので、本はまだ読んでいないが、とりあえずどんなものか動かしてみることにした。

ファイルを展開すると色々とファイルがあるが、Prologの実装の本なので順を追って複雑になっているらしい。
Mini-Prolog-IIが最終版のようなので、こちらを動かすことにする。

$ cd microPrologII/
$ make

とすると、V4.lspというファイルができる。
540行目付近で;の付け忘れがあるので修正しよう。

544c536
<           (push_cont)                 ; saves current cont.
---
>           (push_cont) saves current cont.

さて、このファイルを実行すると

Mini-PrologII

; Loading text file /l/src/rw/mini-prolog-ii/mlg.Start /l/src/rw/mini-prolog-ii/mlg.Start

| ?-

のように初期化ファイルを読み込んでPrologが開始される。S式PrologではなくDEC-10 Prolog文法らしい。

| ?- conc([a,b,c],Xs,[a,b,c,d,e,f]). % conc = append
xs = [d,e,f]
no More

Zebraベンチを走らせる

とりあえず動いたが、ファイルを読み込ませる方法が分からないので、とりあえずLisp側から読み込ませてみることにした。

(defun mp2-load (file)
  (let ((*readtable* mini-prolog-ii::*mini-prolog-ii-readtable*)
        (*package* (find-package :mini-prolog-ii)))
    (load file)))

(mp2-load "zebra.mpl")

という風に読み込ませる。

ベンチのコードは、下記のようにしたが、これまで測定に利用してきたAllegro PrologのページのものをMini-Prolog-IIで動くように調整したもの。

% -*- Mode: prolog -*-
%
% This file for benchmarking against Mini-Prolog-II.
%

$ member(Item, [Item|_]). $ member(Item, [_|T]) :- member(Item, T).

$ nextto(X, Y, List) :- iright(X, Y, List). $ nextto(X, Y, List) :- iright(Y, X, List). $ iright(Left, Right, [Left, Right | _]). $ iright(Left, Right, [_ | Rest]) :- iright(Left, Right, Rest).

$ zebra(H, W, Z) :- eq(H,[house(norwegian, _, _, _, _), _, house(_, _, _, milk, _), _, _]), member(house(englishman, _, _, _, red), H), member(house(spaniard, dog, _, _, _), H), member(house(_, _, _, coffee, green), H), member(house(ukrainian, _, _, tea, _), H), iright(house(_, _, _, _, ivory), house(_, _, _, _, green), H), member(house(_, snails, winston, _, _), H), member(house(_, _, kools, _, yellow), H), nextto(house(_, _, chesterfield, _, _), house(_, fox, _, _, _), H), nextto(house(_, _, kools, _, _), house(_, horse, _, _, _), H), member(house(_, _, luckystrike, oj, _), H), member(house(japanese, _, parliaments, _, _), H), nextto(house(norwegian, _, _, _, _), house(_, _, _, _, blue), H), member(house(W, _, _, water, _), H), member(house(Z, zebra, _, _, _), H).

% This runs the query a single time: % ?- zebra(Houses, WaterDrinker, ZebraOwner). %

$ zebra1(Houses, WaterDrinker, ZebraOwner) :- zebra(Houses, WaterDrinker, ZebraOwner), !.

$ zebran(X,X). $ zebran(N,Limit) :- zebra1(Houses, WaterDrinker,ZebraOwner), plus(N,1,N1),!, zebran(N1,Limit).

実は、調整したといいつつ、Mini-Prolog-IIは匿名変数の_をサポートしていないらしい。
シンボル名が被らないように記述すれば良いが面倒なので処理系を改造することにした。
といっても簡単な改造で、_シンボルだったら(gensym)するというだけのもの。

(defun read_atom (ch)                   ; next normal symbol
  (do ((lch (list ch) (push (read-char) lch)))
      ((not (alphanum (peek-char)))
       (let ((sym (implode (reverse lch))))
         (if (string= "_" sym)
             (gensym "_")
             sym)))))

これで匿名変数がちゃんとサポートできているのかは良く分からないが、とりあえず上手く動いているようだ。

| ?- zebra1(Houses,WaterDrinker,ZebraOwner).
zebraowner = japanese
waterdrinker = norwegian
houses = [house(norwegian,fox,kools,water,yellow),
          house(ukrainian,horse,chesterfield,tea,blue),
          house(englishman,snails,winston,milk,red),
          house(spaniard,dog,luckystrike,oj,ivory),
          house(japanese,zebra,parliaments,coffee,green)]
no More

計時

備え付けで、cputimeというものがあるが測定しづらいので、timeという単なる時間を取得する述語を作成し時刻差を計算することにする。

繰り返しには、zebranという任意の回数繰り返すものを作成し利用してみた。
しかし、18回以上回すとオーバーフローするので、15回位の繰り返しとし、1000回繰り返した場合の予測とする。

Allegro Prologと比較したいので、計時プラットフォームはAllegro CL 8.2。

time(S),!,
zebran(0,15),!,
time(E),!,
minus(E,S,Time),!.

を実行すると、15回で大体470msなので、1000回回したら31秒という所だろうか。

過去にZebraベンチを同じ条件で計時したことがあるが、PAIPrologが11秒だったのでその3倍位になる。

最適化宣言をして、それだけで速くなったら儲け物なので、次に下記のような宣言をし、

(declaim (optimize (speed 3) (safety 0) (debug 0) (compilation-speed 0)))

再度計時してみたが、これだけで12.4秒位までは速くなった。大体PAIPrologと一緒のタイムだ。

ちなみに、SBCLでは、10秒、LispWorksでは11秒と若干速くなるらしい。

結び

AZ-Prolog並のスピードを出すAllegro Prologは、PAIPのPAIPrologをチューニングしたものがベースになっているが、元のPAIPrologもまあまあ速いようだ。

Mini-Prolog-IIをいじって高速化できたら楽しいので、The Implementation of Prologを読んでPrologの実装について勉強することにしよう。

なお、今回の計時で利用した一式はGitHubに置いてみてある

関連記事


HTML generated by 3bmd in LispWorks 7.0.0

Common Lispのお宅拝見: CMU Common Lisp篇

Posted 2017-06-01 07:09:45 GMT

今回は、CMU Common Lispのcl-userを眺める。

現在でも開発が続いているCommon Lispの処理系の系列としては、CMU Common Lisp(CMUCL)は、1980年あたりのSpice Lispから連綿と続いており、もうすこしで40年になろうとしている。
CMUCLからフォークしたSBCLの方が現在は開発・利用とも活発だが、この系統はCLtL1がSpice Lispのマニュアルを下敷として作成されていたり、Common Lispの歴史と関係が深い。
他にCMUCLからフォークしたものとしては、商用処理系のScieneer Common Lispがあり、こちらはSBCLと同じく64bit化もされている。

cl-userパッケージの構成

さて、cl-userの構成だが、拡張ユーティリティのextentions(ext)パッケージをuseしている。
extパッケージは、便利関数・マルチプロセッシング・拡張機能等で250位の関数・変数が定義されている。

古くからあるものを一つ紹介するとcollectのようなものがある。

(collect ((acc '(-1)))
  (dotimes (i 10)
    (acc i))
  (acc))(-1 0 1 2 3 4 5 6 7 8 9)

また、CMUCLは、double-doubleというdoubleを二つ使って精度を高めた浮動小数点数形式をサポートしているので、そのあたりの定義がある。

(let ((*read-default-float-format* 'double-double-float))
  (read-from-string "3.14159265358979323846264338327950288419716939937511"))
→  3.1415926535897932384626433832795w0
    52

また面白いのが、2000年代位にAllegro CLの機能を取り込んでいて、階層パッケージがあったり、古くからあるencapsulateを土台としてAllegro CL互換のAdvice機構(fwappers)を構築したりしているらしい(fwrappersはextパッケージにはなく別パッケージ)

さて毎度確認しているtruefalseだが、CMUCLにも実装されていなかった。あれれ。

結び

Common Lisp誕生時から存在する、というか元になったものの一つであるSpice Lispの系統が未だに一番人気があるというのも面白い。


HTML generated by 3bmd in LispWorks 7.0.0

Common Lispのお宅拝見: Kyoto Common Lisp篇

Posted 2017-05-30 12:55:12 GMT

今回は、Kyoto Common Lispのcl-userを眺める。

MACLISP系方言をまとめようというのがCommon Lispの発端であったが、それ故MACLISP系方言に親しんだ人達には暗黙の前提があり作られた処理系にはそれが反映されていた。
Kyoto Common Lisp(KCL)は、新規に開発されたため、そのようなCommon Lisp仕様の暗黙の前提を洗い出し、より堅実な仕様を作ることに貢献したとされている。

そんなKCLだが、KCLからは沢山の支流があり、現在も活発なものの代表例としては、Embeddable Common Lisp(ECL)と、GNU Common Lisp(GCL)位だろうか。
GCLは、KCLからAKCLとなり、そこからGNUに渡った系譜で、ANSI CL化はされないままMaxima等の基盤として現在でも利用されているが、gmpを取り込んだりして開発体勢は死んでいないらしい。
また、ECLは、ANSI CL化された系統でUnicode化もされている。ManKai CL(MKCL)はECLからのフォークでECLに対して独自の味付けをしている、という所だろうか。

cl-userパッケージの構成

さて、cl-userの構成だが、KCLはCLtL1なので、userの構成の述べる。
userの構成は非常にシンプルで、lispをuseしているだけというもの。

ECLはANSI CL化されているが、こちらのcl-usercommon-lispをuseしているだけ。

MKCLは少し独自色があり、mkclパッケージをuseしていて、str+等独自なユーティリティが定義してある。

(str+ "foo" "bar" "baz")
→"foobarbaz"

(make-sequence 'octets 10)
→ #(0 0 0 0 0 0 0 0 0 0)

(type-of (make-sequence 'octets 10))(vector natural8 10)

GCLは、defpackageパッケージをuseしているが、CLtL1にはdefpackageがなかったので、別途defpackageが定義されたパッケージをuseしている。若干ANSI CL化されているとも言えるだろう。

さて毎度確認しているtruefalseだが、KCL系には実装されていなかった。ちょっと残念。

結び

KCL系は何の味付けもないcl-userという結果だったが、KCLらしいといえば、そうなのかもしれない。


HTML generated by 3bmd in LispWorks 7.0.0

Common Lispのお宅拝見: Clozure Common Lisp篇

Posted 2017-05-17 15:54:53 GMT

今回は、Clozure Common Lispのcl-userを眺める。
Clozure CLの系統の歴代の処理系を眺めてみたが、どうやら、cl-userの構成は、Macintosh Common Lisp 2.0(1991)で大まかな所が決まったようだ。

Clozure CLの系統の大まかな流れとしては、1987年にCoralがCoral Common Lispを発売し、間も無くFranzと共同で販売することになり年内に、Macintosh Allegro Common Lisp(MACL)となる。

1991年にMCL 2.0となるが、この頃の販売元はAppleで、言語仕様は、この頃出版されたCLtL2を追い掛けたものとなっている。

CLtL1の仕様では、基本パッケージとして、lispusersystemが必須だったが、ANSでは、common-lispcommon-lisp-userとなり、systemは必須ではなくなった。
名前が変更になった理由は、CLtL1仕様とそれ以降の仕様を一つのイメージに同居させるため等々だったようだが、実際には、lispパッケージをclパッケージという名前にしてしまうことが多かったようだ(Allegro CL、LispWorks等)。

しかし、MCLでは、このLISP-PACKAGE-NAME:COMMON-LISPにきっちり対応したようで、MCL 2.0でばっさりとlispuserを廃止して新しい名前にし、旧パッケージは、(require 'lisp-package)で読み込むようになっている。
(なお、これは現在のClozure CLでも同じ。)

関数の仕様の変更もしっかり反映しているので、

(cl:functionp 'list)
→ nil

(lisp:funcionp 'list) → t

のようにLISP-PACKAGE-NAME:COMMON-LISPで検討されていたことが、そのまま実現できている。

このように対応した処理系は、MCLの他にSymbolics CLがあるが、現在CLtL1時代のコードを動かそうとすると、きっちり分かれていた方が可搬性が高いようだ。

当時は移植性を考えてずるずるとlispclと移行した処理系が多かったのだと思うが、結局、前後の仕様が混ざる結果となり、移植性が損なわれることになったように思える。

ちなみに、1.0系統で利用されていたDresher氏が設計したオブジェクトシステムのObject LISPは削除されMCL 2.0からCLOSが搭載されることになった。

cl-userパッケージの構成

さて、CCL系統がdefpackageした時にデフォルトでuseされるパッケージは、clccl

(defpackage :foo)
→ #<Package "FOO">

(package-use-list :foo)(#<Package "CCL"> #<Package "COMMON-LISP">)

cclパッケージは、もともとcoral clの略なのだと思うが、MCL時代もそのまま使われ続け、さらに、Open MCLが処理系名を変更する際には、cclを活かしてClozure CLとしたので原点回帰した。

そのcclパッケージだが、700〜1000を越えるシンボルがエクスポートされている。
古くからある他の処理系と同じく、かなりのごった煮パッケージだが、ユーティリティや処理系拡張が占めている。
さらにMCL時代は、FREDというEmacsが同梱されていて、これがcclパッケージにいるので、これが大分大きくしているようだ。

MCL 2.0位の時期は、処理系拡張はとにかくcclパッケージに入れるという感じだったようだが、Clozure CLでは、多少分別されるようになったらしい。

また、今回も恒例のユーティリティに定義されていることが多いtruefalseの調査を実施。
確認できる限りでも、Macintosh Allegro Common Lisp 1.2.2(1989)時代からCCLパッケージに存在するらしい。

(mapcar #'true lambda-list-keywords)(t t t t t t t t)
(mapcar #'false lambda-list-keywords)(nil nil nil nil nil nil nil nil)

結び

現在のClozure Common Lispの源流であるCoral Common Lispが登場してから30周年らしい。
Spice LispがIBM RT PC上のMachに移植されCMU Common Lispとなったのが1987年、Allegro CLの最初の実装(TEK Common Lisp)が1986年、最初のCLISPが登場したのが1987年、のようだが、現在も生き残っている処理系が続々と30周年を迎えている。
そもそも生き残っているというのが凄いが。


HTML generated by 3bmd in LispWorks 7.0.0

Common Lispでローカル定数の構文

Posted 2017-05-15 16:31:37 GMT

C#にもローカル定数の構文が導入されるとのことだが、Common Lispにも欲しいという声を目にしたので、ちょっと試しに作ってみた。

(defmacro const (var)
  `((lambda () ,var)))

(defmacro ket ((&rest binds) &body body) (loop :for (var val) :in binds :for gvar := (gensym (string var)) :collect `(,gvar ,val) :into gs :collect `(,var (const ,gvar)) :into cs :finally (return `(let (,@gs) (symbol-macrolet (,@cs) ,@body)))))

const構文にあまり意味はなく、直接lambdaを書いてしまっても良いが、気分的に定義してみた。

(defun fib (n)
  (declare (optimize (speed 3) (safety 0) (debug 0) (hcl:fixnum-safety 0))
           (type fixnum n))
  (ket ((n n))
    (if (< n 2)
        n
        (+ (fib (1- n))
           (fib (- n 2))))))

こんな感じに書いてもコンパイラが最適化してくれるので、ketは無かったことになることが多いだろう(少なくともLispWorksではそうなる)

(defun fib (n)
  (declare (optimize (speed 3) (safety 0) (debug 0) (hcl:fixnum-safety 0))
           (type fixnum n))
  (ket ((n n))
    (if (< n 2)
        n
        (+ (fib (decf n))
           (fib (decf n))))))

こういうのはマクロ展開時にエラーになる。

このketは、定数を宣言しているのではなく、setfsetqでエラーを起すようにしたもの。

(let ((x 42))
  (setf (const x) 42))

でエラーになるようにしたと考えれば判り易いだろう。
それ故、setfsetq時のエラー内容は全く定数云々の件とは異なるので、この辺りに手抜き感が漂う。

defconstantを使うものに展開するという手もあるが、defconstantはトップレベルに置かないと上手く機能せず、そこをコードウォークでやりくりするにしても色々面倒なので、まあこの辺りで手を打ってみた。

ちなみに、ローカルな定数構文の提案は、Common Lispの仕様策定時にはあり、1987年にlet-constant/constantletという代物が提案されている。
(declare (constant foo))のような宣言も提案されていたようだが、しかし、紆余曲折でどこかに行ってしまったようだ。

結び

やっぱりコンパイラで対応してくれないときびしい。


HTML generated by 3bmd in LispWorks 7.0.0

Common Lispのお宅拝見: Xerox Common Lisp篇

Posted 2017-05-09 21:09:29 GMT

今回は、Xerox Common Lisp(Medley3.5)のxcl-userを眺める。
Medleyは、XeroxのLispマシンであるInterlisp-Dマシンの仮想マシン版。
Interlisp-DマシンもCommon Lispの普及に応じて取り込んだため、Common Lispも使えるのだった。
Interlisp-Dで実装されているため、マクロ展開などではInterlisp-Dの関数等が見えてたりして中々面白い。

2002年の時点では、$400から$2000位で各種環境が販売されていたようだが、現在ではどうなのだろうか。

Medleyの導入については過去に幾つか書いているので興味のある方は参照されたい。

さて、Medleyは、ANSI CL化以前という所なので、ユーザーのホームパッケージは、cl-userではなく、userである。
しかし、Xerox CL(XCL)では、xcl-userというのを用意してこちらをデフォルトにしているので、今回は、こちらを紹介することにする。
ちなみに、userは、lispをuseしているだけのパッケージとして用意されてはいる。

(package-use-list :xcl-user)
→ (#<Package LISP> #<Package XEROX-COMMON-LISP>) 

となっている。

xerox-common-lisp(xcl)パッケージは、大体180位のシンボルで、大抵の処理系と同じく、マルチプロセス等の拡張機能、ユーティリティで占められている。
ANSI CL規格以前にはdefpackageは無いが、xclパッケージには用意されているので試してみると、

(defpackage :foo)
→ #<Package FOO>

(package-use-list :foo)(#<Package LISP>)

となり、lispパッケージがuseされるのみ。

ざっと眺めて面白そうなユーティリティを紹介してみると、

XCL:WITH-COLLECTION

SBCLなどにもあるリスト集積のユーティリティ

(with-collection
  (collect 'x)
  (collect 'x))(x x)

XCL:DESTRUCTURING-SETQ

destructuring-bindもCLtL1には存在しなかったが、xcl:destructuring-bindと一緒にsetq版も定義されている。

(let (a d)
  (destructuring-setq ((a . d)) '((1 . 2)))
  (list a d))(1 2)

また、今回も非常にどうでも良い所ではあるが、ユーティリティに定義されていることが多いtruefalseを調べてみた。
XCLには存在したが、もしかしたらCLtL1な処理系でお馴染だったのかもしれない。

(mapcar #'true (il:for i il:from 1 il:to 20 il:collect i))(t t t t t t t t t t t t t t t t t t t t) 
(mapcar #'false (il:for i il:from 1 il:to 10 il:collect i))(nil nil nil nil nil nil nil nil nil nil) 

結び

そういえば、Interlisp-Dといえば、LOOPSなのだが体験できる環境がなく試せていない。
実機のディスクイメージは多数公開されているので、Medley以外でエミュレータが実現されればもしや使えるかも……。


HTML generated by 3bmd in LispWorks 7.0.0

Common Lispのお宅拝見: Allegro Common Lisp篇

Posted 2017-05-09 06:00:20 GMT

今回は、Allegro Common Lisp 10.1のcl-userを眺める。
Allegro CLもLispWorksと同様に様々なプラットフォームで稼動しIDEも付属するが、LispWorksと違ってGUI環境のパッケージは別になっていて、CUIの時は通常読み込まれない。
とりあえず、cl-userの中身を確認してみると、

(package-use-list :cl-user)
→ (#<The COMMON-LISP package> #<The EXCL package>) 

となっている。
GUI環境では、cl-userではなく、cg-userがホームパッケージとなり、cgパッケージがこれに加わる。

このexclパッケージがかなり大きく、シンボル数735、そのうち関数513、変数53というごった煮ユーティリティパッケージとなっている。

defpackage時でオプションを省略した場合のデフォルトでは、clしかuseされず、割合に素直な印象。
とはいえ、結局処理系ごとにまちまちなので、(:use :cl)と明示的に書くことになるのだが。

(defpackage :foo)
→ #<The FOO package>

(package-use-list :foo)(#<The COMMON-LISP package>)

ちなみに、exclいうのはExtended Common Lispの略で、Allegro CLの元の名前である。
Allegro CLは、Tektronix 44xxシリーズで稼動するCommon Lisp処理系(TEK CL)として誕生したが、Franzの内部的には、Extended CLとしていたらしい。

その後、Macintoshで稼動するCoral CLをFranzが共同で販売した際に、Macintosh Allegro Common Lispとし、その後、既存のExCLもAllegro CLと名称が変更になった。

閑話休題。Allegro CLのcl-userパッケージの特徴だが、正規表現ライブラリがデフォルトの状態で使えたりすることだろうか。

(re-let "(.*)\\s+(.*)\\s+(.*)" "foo bar baz"
        ((x 3) (y 1) (z 2))
  (list x y z))

("baz" "foo" "bar")

正規表現がデフォルトのcl-userで使える処理系というのは案外少ない。筆者が知る限りというか唯一かもしれない(CLISPもそうかと思ったがregexpパッケージはuseされていないようだ)。

その他は、LispWorksと同じくCLtL1時代からのユーティリティが多数定義してある。
ここ数年マルチスレッド対応が徹底してきた為、exclパッケージはさらに肥大したようだ。

また、今回も非常にどうでも良い所ではあるが、ユーティリティに定義されていることが多いtruefalseを調べてみたが、意外にもエクスポートされていなかった。
筆者が定番と思っていただけだったかもしれない……。

(mapcar #'excl::true (loop :repeat 20 :for i :from 1 :collect i))(t t t t t t t t t t t t t t t t t t t t) 
(mapcar #'excl::false (loop :repeat 20 :for i :from 1 :collect i))(nil nil nil nil nil nil nil nil nil nil) 

結び

Allegro CLの‘excl’のように処理系独自のユーティリティパッケージは非常に面白く、詳細に眺めてみたい所なのだが膨大できりがない。
なんとかコンパクトにまとめられると良いのだが……。


HTML generated by 3bmd in LispWorks 7.0.0

Common Lispのお宅拝見: LispWorks篇

Posted 2017-05-07 15:23:16 GMT

Common Lispを利用する上で普段何気無く利用しているcl-userだが、実は処理系毎にユーティリティの充実度等が結構違っている。
そこで、ホームパッケージであるcl-userパッケージを処理系毎に観察してみよう。

今回は、LispWorks 7.0 を眺める。
LispWorksは様々なプラットフォームで稼動するのでプラットフォーム依存な所はあるのだが、cl-userのが取り込んでいるパッケージは下記の通りで、LISPWORKS(lw)とHARLEQUIN-COMMON-LISP(hcl)を取り込んでいる。

(package-use-list :cl-user)

→ (#<The COMMON-LISP package, 2/4 internal, 978/1024 external> #<The HARLEQUIN-COMMON-LISP package, 0/8 internal, 353/512 external> #<The LISPWORKS package, 0/4 internal, 224/256 external>)

defpackageした際に:useを省略するとデフォルトのパッケージが取り込まれるが、上記3つが:useされるのがLispWorksの標準。

ちなみに、Harlequin Common LispというのはLispWorksを元々作っていた会社がHarlequinということに由来する。
Harlequin社は、〜Worksという名前で製品を作ることが多かったが、元々は、Harlequin CLを中心とした開発環境がLispWorksということだったらしい。

lwパッケージとhclパッケージの使い分けが判然としないが、hclパッケージの方がCLtL1時代からのユーティリティが多い気がするが、lwhclを合せて600近くのシンボルがあるので、結構ごちゃごちゃしているなあという印象はある。

これらユーティリティで有名なところでは、when-letif-letwith-unique-namesrebindingsplit-sequence辺りだろうか。
with-unique-namesは、with-gensymrebindingonce-onlyとして知られているマクロだが結構見掛けることは多いかと思う。
split-sequenceはQuicklispにもあるユーティリティと同名だが、LispWorksがオリジナルなのかもしれない(とはいえ微妙に仕様が違う)

また、オリジナルのloopにあったユーザー定義の構文がデフォルトで使えるので、

(define-loop-macro for)
(define-loop-macro repeat)
(define-loop-macro with)

(for i :from 0 :repeat 10 :collect i) ;=> (0 1 2 3 4 5 6 7 8 9)

こんなこともできるし、さらにユーザーがデータ型の処理方法を任意に定義することもできる(defloop等)

また、非常にどうでも良い所ではあるが、ユーティリティに定義されていることが多いtruefalseも用意されている。

(mapcar #'true (repeat 20 :for i :from 1 :collect i))
;=> (t t t t t t t t t t t t t t t t t t t t) 
(mapcar #'false (repeat 10 :for i :from 1 :collect i))
;=> (nil nil nil nil nil nil nil nil nil nil) 

Common Lispにはconstantlyがあるので不要に思えるのだが、用意している処理系は結構ある。

結び

以前から、処理系ごとにcl-userを比較してみていたが、これまで資料は貯めていたものの何故か書いたことがなかった。
今後、暇潰しに他の処理系についても書いてみるつもりである。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: 素数夜曲―女王陛下のLISP: B.5.3 構文の拡張

Posted 2017-05-07 07:04:39 GMT

今回は、素数夜曲―女王陛下のLISPのマクロの箇所を読む。
本書は、数学の本なのだが、Lisp(Scheme)についての内容が半分を越えるということで知られているらしい。
マクロの解説もあったので眺めてみた。

B.5.3 構文の拡張

著者は、例外をできるだけ少なくなるようにプリミティブまで分解した結果、利用者がプリミティブを組み合せて目的の物を作るというScheme流のスタイルが生れてくるという解釈をしている。
関数と残りの例外である特殊形式まで分解した結果、それらを組み合せるというマクロが生きてくる、という解釈。
綺麗な解釈だとは思うが、正直考え過ぎかなと思った。
ただ、Lispマクロは特殊形式を減らそうというのが出自であったので、著者の考えと順番は逆にはなるが、プリミティブに分解する方向では同じかもしれない。

さて、マクロの解説だが、構文の拡張の例として、ifthenelseのキーワードをつける例を紹介。syntax-rulesを用いるので特にややこしい説明はない。
次に、notandが合体したnandincdecを作る。

最後に、delayed consの構文を定義するということで、s-conss-cars-cdrを定義し、delayforceを隠蔽してみせる。
関数でも定義可能なものになっているので、何故マクロを使ったのかは分からないが、この章は構文の拡張がお題だからだろう。

結び

数学の本なのに一応Schemeマクロの解説までしてあるというのは凄いと思う。


HTML generated by 3bmd in LispWorks 7.0.0

CL-Cleanup、CL-Compilerメーリングリストをまとめてみた

Posted 2017-05-04 07:45:45 GMT

なんとなくsaildart.orgからCL-Cleanup、CL-Compilerメーリングリストのファイルを抜き出して纏めてみた。
メーリングリストの期間や、元データ等の詳細は、ml.cddddr.orgのフロントページに記載してある。

CL-Cleanupの方は、HyperSpecの付録として配布されているので割合に目にしたことがあるかもしれない。
あのイシューを議論していたのが、CL-Cleanupだったらしい。

CL-Cleanupを眺めて興味を持ったらHyperSpecのまとめを参照してみるというのも良いかもしれない。

どうも:1をどういう風に解釈したら良いかも議論して決めたことだったらしく感心してしまう。

CL-Compilerの方も眺めてみると興味深いことが多いというか、Common Lispの理解が深まりそうな気がする。


HTML generated by 3bmd in LispWorks 7.0.0

Common Lispのeltとnthの違い

Posted 2017-05-02 04:41:51 GMT

Common Lispのeltsequence全般に使えるが、リストで使う場合には、nthとは微妙に挙動が違う。

(let ((u '(0 1 2 3 4 5 6 7 8 9) ))
  (list (nth 100 u)
        (multiple-value-list (ignore-errors (elt u 100)))))
;=> (nil (nil #<conditions:index-out-of-range 40202E27E3>)) 

eltは上記のようにシークエンスの範囲外ではエラーとなるが、インデックスはvalid array indexであることと定められているのであった。

対してnthはインデックスは、a non-negative integerである。 範囲外ではnilを返す。

ちなみに似たようなものには、endpnullの関係がある。

(let ((u '(a . d)))
  (list (null (cdr u))
        (multiple-value-list (ignore-errors (endp (cdr u))))))
;=> (nil (nil #<type-error 402015AD53>)) 

結び

どこかで書いたことがある気がしたが、どこに書いたのか思い出せないのでまた書いてしまった。
チェックも厳しいのでeltで済むなら、elt書いた方が良いだろう。
ちなみに、eltで書いても最適化すれば大抵の場合nth同等になる。


HTML generated by 3bmd in LispWorks 7.0.0

Seriesの色々

Posted 2017-04-27 16:36:10 GMT

久々にSeriesのブログ記事を見掛けたが、ブログ中にこんな疑問があった

``上のコード、実行するたびに以下のようなwarningが出るんですよね。なんとかならないものか。''

これは大抵の場合、seriesのパイプラインに載ってないのが理由である。
Seriesでは最適なコードを出すには色々な制約があり、色々繁雑な作法があるのだった。

この流れて行くと元のコードをSeries的に最適化して、やったね!という締めになりそうだが、このコードを最適化するのはどうも大変っぽいので別の書き方をすることにした。

とりあえずは、パッケージの定義から

(defpackage :Z
  (:use :cl :series))

(in-package :Z)

(series::install :implicit-map T)

まず、Seriesを最適化するにはseries::installの実行が大切である。
リーダーマクロが導入されるに留まらず、letdefunfuncallが改造されてしまう。この辺りは好き嫌いが分かれるだろう。ちなみに筆者はあまり好きではない。

implicit-mapはデフォルトではnilだが筆者はこの怪しい機能が好きなのでTにしたい。

implicit-mapTだとこのように書ける

(let ((e (scan-range))
      (s (scan "響け!ユーフォニアム")))
  (collect (list e s)))
;=> ((0 #\響)
;   (1 #\け)
;   (2 #\!)
;   (3 #\ユ)
;   (4 #\ー)
;   (5 #\フ)
;   (6 #\ォ)
;   (7 #\ニ)
;   (8 #\ア)
;   (9 #\ム)) 

ぱっと見普通だが良く考えると色々変なことになっている。

恐らくこの機能は、seriesの前身のLetSの構文を実現するものなのではないかなと考えているが実際はどうなのだろう。

閑話休題。それで、とりあえず、clojureのcycleみたいなユーティリティを考えてみる。

(defun cycle (seq)
  (declare (optimizable-series-function 1)) 
  (let ((len (length seq)))
    (scan-fn '(values character fixnum) 
             (lambda () (values (elt seq 0) 0))
             (lambda (c prv &aux (cur (1+ prv))) 
               (declare (ignore c))
               (values (elt seq (mod cur len)) cur))
             (constantly nil))))

(subseries (cycle "響け!ユーフォニアム") 0 20) ;=> #Z(#\響 #\け #\! #\ユ #\ー #\フ #\ォ #\ニ #\ア #\ム #\響 #\け #\! #\ユ #\ー #\フ #\ォ #\ニ #\ア #\ム)

(declare (optimizable-series-function 1))が肝だが、defunもSeriesの独自定義のものに差し換えられており、後々ループに組み直す為に定義時に中身が分解されて格納されたりしている。
defunマクロを展開すると通常のcl:defunとは似ても似つかない内容になっているので眺めてみよう。

それで、本体だが、一文字ずつずらしながら改行されているということは、該当の場所の文字を改行に置き換えてしまえば良いのではないかということで、このように書いた

(defun 響け!ユーフォニアム ()
  (let* ((str "響け!ユーフォニアム")
         (len (length str)))
    (collect-ignore
     (#2M(lambda (c i)
           (write-char (if (= len (mod i (1+ len))) #\Newline c)))
         (cycle str)
         (scan-range :length (* len (+ 2 len)))))))

なお、implicit-mapが有効なので、#2Mは取ってしまえるので、こうなる

(defun 響け!ユーフォニアム ()
  (let* ((str "響け!ユーフォニアム")
         (len (length str)))
    (collect-ignore
     ((lambda (c i)
        (write-char (if (= len (mod i (1+ len))) #\Newline c)))
      (cycle str)
      (scan-range :length (* len (+ 2 len)))))))

さらにlambdaletに纏めることが可能。

(defun 響け!ユーフォニアム ()
  (let* ((str "響け!ユーフォニアム")
         (len (length str))
         (c (cycle str))
         (i (scan-range :length (* len (+ 2 len)))))
    (collect-ignore
     (write-char (if (= len (mod i (1+ len))) #\Newline c)))))

(響け!ユーフォニアム) ;>> 響け!ユーフォニアム ;>> け!ユーフォニアム響 ;>> !ユーフォニアム響け ;>> ユーフォニアム響け! ;>> ーフォニアム響け!ユ ;>> フォニアム響け!ユー ;>> ォニアム響け!ユーフ ;>> ニアム響け!ユーフォ ;>> アム響け!ユーフォニ ;>> ム響け!ユーフォニア ;>> 響け!ユーフォニアム ;=> nil

そして、この定義をマクロ展開すると、こんな恐しいことになっている (実行可能なように#:は取ってある)

(defun 響け!ユーフォニアム ()
  (cl:let* ((str "響け!ユーフォニアム"))
    (cl:let (len)
      (setq len (length str))
      (cl:let (out-137857)
        (setq out-137857 (* len (+ 2 len)))
        (cl:let (out-137847)
          (setq out-137847 (length str))
          (cl:let (out-137846 out-137845)
            (setq out-137846
                  #'(lambda (c prv &aux (cur (1+ prv)))
                      (declare (ignore c))
                      (values (elt str (mod cur out-137847)) cur)))
            (setq out-137845 (constantly nil))
            (cl:let (state-137844
                     (state-137843 0)
                     items-137848
                     (items-137842 0)
                     (i (coerce (- 0 1) 'number))
                     (counter-137854 out-137857))
              (declare (type fixnum state-137843)
                       (type fixnum items-137842)
                       (type number i)
                       (type fixnum counter-137854))
              (locally
                (declare (type character state-137844)
                         (type character items-137848))
                (values (cl:let* ()
                          (cl:multiple-value-bind (|Store-Var-137865|
                                                   |Store-Var-137866|)
                                               ((lambda () (values (elt str 0) 0)))
                            (cl:let* ()
                              (values (setq state-137844 |Store-Var-137865|)
                                      (setq state-137843 |Store-Var-137866|))))))
                (tagbody
                 ll-137864 (if (cl:funcall out-137845
                                           state-137844
                                           state-137843)
                               (go series::end)
                               nil)
                 (setq items-137848 state-137844
                       items-137842 state-137843)
                 (values (cl:let* ()
                           (cl:multiple-value-bind (|Store-Var-137867|
                                                    |Store-Var-137868|)
                                                (cl:funcall out-137846
                                                            state-137844
                                                            state-137843)
                             (cl:let* ()
                               (values (setq state-137844
                                             |Store-Var-137867|)
                                       (setq state-137843
                                             |Store-Var-137868|))))))
                 (setq i (+ i (coerce 1 'number)))
                 (if (not (plusp counter-137854)) (go series::end) nil)
                 (cl:let* ()
                   (cl:let* ()
                     (cl:let ((|Store-Var-137869|
                               (- counter-137854 1)))
                       (setq counter-137854 |Store-Var-137869|))))
                 (write-char (if (= len (mod i (1+ len)))
                                 #\Newline
                                 items-137848))
                 (go ll-137864)
                 series::end)
                nil))))))))

おわかり頂けただろうか……別々に定義した関数の中身が合体していることが分かると思う。

結び

筆者も以前はSeriesを好んで利用していたが最近は面倒なのでSeriesを使って書いたりしていない。

どうもSeriesを使って何か書いていると、Seriesが上手く最適化されないなーなどとチューニングの為に横道に逸れてしまうことが多いのだった。

Seriesだと繰り返しを関数の組み合わせのように書けるが、実際の所はまさしく黒魔術なマクロでループに式を変形していて、Seriesの作法を知らないとビシッとループには展開されない。
綺麗にループに展開されない場合は、非効率なコードが出てしまうが、冒頭の警告の話は、このような背景による。

この辺りはコンパイラがすべきことなんじゃないかなあと思ってしまうが、マクロの可能性の一つではあるのかもしれない。

とりあえず、Seriesの標準のユーティリティにあまり使い易いものはないが、Clojureあたりを参考にユーティリティを作る所から始めれば、Seriesも快適に使えたりするのかもしれない。


HTML generated by 3bmd in LispWorks 7.0.0

教養としてのCommon Lisp

Posted 2017-04-26 17:35:08 GMT

Common Lispを学ぶにも色々な動機があるが、言語オタクがCommon Lispを学ぶ上で、他の言語とはちょっと毛色が違ったアピールポイントを筆者なりに考えてみた(暇だったので)。

30年前にはかなり尖っていたかもしれない仕様だが、今となってはかなり他の言語に機能がキャッチアップされているCommon Lisp。
まず、筆者的には、対話環境がすなわち言語処理系ということがCommon Lispの特長かなと思っているのでその辺りを推したい。
evalが発展して対話環境ともなり、コンパイラとも連携しているということが醍醐味なのかなと思っている。
なお突き詰めれば、二村射影とか部分評価とかその辺りに関連していくのかもしれないが筆者は良く知らない。

それと、良く言われるProgrammable Programming Languageということ。
Lispを書くようになると当たり前に感じられることだがLispの特長だろう。
Common Lispを書けば、Programmable Programming Languageを実現するための道具立ても豊富であることも分かると思う。

などなど考えて学習トピックを並べてみた

  • 始めに対話環境があり、それがコンパイラを含むようになったという流れ

    • ランタイムにコンパイラを含め全ての機能が含まれているということ(実行時コンパイルなど)
    • インタプリタ動作とコンパイラ動作に矛盾がない仕様とその実現努力
  • Code as data、programmable programming languageということ

    • リスト処理/コード処理
    • ユーザーが目的に応じて処理系をカスタマイズするということ

      • ユーザー拡張の例:フックできる場所の例とフックを実現する機構
      • evalを直接拡張していた時代のおさらい
      • リード時、コンパイル時、ロード時評価
      • リーダーマクロ、マクロ、マクロ展開時のフック、MOP(オブジェクトシステムの動作の規約に則ったカスタマイズ)、アドバイス機構
  • 言語の策定プロセス:取捨選択のプロセス

    • 過去の処理系/Lisp文化との互換性の取捨選択
    • Lisp専用マシンから汎用マシンをメインのターゲットにしたということ
  • より発展的な処理系との比較: Racketなど (素朴 vs 先進的機能)

    • Lisp-1 vs Lisp-2
    • サブセット的な規格との比較: Common Lisp vs ISLISP (Common Lispでは何が繁雑だと考えられたか)

なお、対話環境については、コードを書いて対話的にデバッグしてみないと分からないのでコードを書くしかないかもしれない。
マクロのデバッグが困難であると先入観を持っている人は多いが、素朴な仕組みなので所詮リスト処理のデバッグが殆どである。
マクロについてはScheme/Racketしか触っていないとちょっと違った印象を持つのかもしれない。

とりあえず、Lisp族を語るならばCommon Lispを知っていることは必須だろうと思う。
何故なら多数の方言があるLispの中で良くも悪くも一つの基準となってしまったのがCommon Lispで、Schemeが反発しつつもCommon Lispの機能を取り込んだりアレンジしたりしているし、Clojureもしかり、Emacs LispはCommon Lispの前身から枝分かれしているが、rmsのCommon Lispへの反発があったり。

なんだかんだでネタ元を知るのが理解の近道だと思うし、まさに教養としてCommon Lispを押えておいた方が言語オタクの井戸端会議は盛り上がると思う。


HTML generated by 3bmd in LispWorks 7.0.0

Zetalispのlambda-macro

Posted 2017-04-23 16:30:51 GMT

今日は一日LMI Lambdaのエミュレータを触っていたが、そういえば、Zetalispにはlambda-macroというものがあることを思い出した。
(ちなみに、LMIのZetalispは、ZetaLISP-Plusらしい……)

lambda-macroの使い所だが、Common Lispでarcのfnを再現することを考えてみよう。
arcのfnは大体Common Lispのlambdaだが、fnには引数の解構destructuring機能がある。

Common Lispのマクロで書くなら、

(defmacro fn ((&rest args) &body body)
  (let ((a (gensym)))
    `(lambda (&rest ,a)
       (destructuring-bind (,@args) ,a
         ,@body))))

こんな感じで、こうなる

(mapcar (fn ((a . d)) (list a d))
        '((0 1 2 3) (1 1 2 3) (2 1 2 3)))
;=> ((0 (1 2 3)) (1 (1 2 3)) (2 (1 2 3))) 

しかしそれで元のlambdaのように

((fn ((a . d)) (list a d)) '(0 1 2 3))

と書けるかというと、こうは書けない。

ここがCommon Lispでは微妙にもどかしい所だが、Zetalispでは、lambda-macroを定義してやれば思ったように書ける。

(deflambda-macro fn ((&rest args) &body body)
  (let ((a (gensym)))
    `(lambda (&rest ,a)
       (destructuring-bind (,@args) ,a
         ,@body))))

((fn ((a . d)) (list a d)) '(0 1 2 3))
;=> (0 (1 2 3)) 

さらに、deffunctionという引数の取り方をカスタマイズした関数を定義する構文が用意されていて、上記のfnを下敷にする関数は、

(deffunction a.d fn ((a . d))
  (list a d))

(mapcar #'a.d '((0 1 2 3) (1 1 2 3) (2 1 2 3)))
;=> ((0 (1 2 3)) (1 (1 2 3)) (2 (1 2 3)))

と書ける。

結び

Lispマシンのマニュアルの解説箇所はこちら

まあ、Lisp-1 ならこういうものは素直に書けるのだが。


HTML generated by 3bmd in LispWorks 7.0.0

LMI Lambdaのエミュレータ:LambdaDelta 公開!

Posted 2017-04-23 05:55:47 GMT

最近、LispマシンのメーリングリストでLMI Lambdaのエミュレータが完成間近であることがアナウンスされていたが、今日LambdaDelta 0.98.1が公開された。
やった!、思いの外公開されるのが早かった。

早速起動したい所だが、ROMや、テープイメージ等を準備する必要がある。

必要なものを取得しつつのビルド手順は下記の通りとなった。
ホストOSは、Debian GNU/Linux 64bit

### LambdaDelta本体の準備

wget https://github.com/dseagrav/ld/releases/download/0.98.1/ld-0.98.1.tar.gz tar xvf ld-0.98.1.tar.gz cd ld-0.98.1 ./configure --without-SDL1 make

### ROMの準備

cd roms wget http://bitsavers.trailing-edge.com/pdf/lmi/firmware/SDU_15165-0003.ZIP unzip SDU_15165-0003.ZIP wget http://bitsavers.trailing-edge.com/pdf/lmi/firmware/VC_MEM.ZIP unzip VC_MEM.ZIP http://bitsavers.trailing-edge.com/pdf/lmi/firmware/Memory/2243902_OF_27S291.BIN

ln -s VC_MEM/U43_2516_2242843-0001_0856TI200038.BIN VCMEM.ROM ln -s 2243902_OF_27S291.BIN MEM.ROM ln -s SDU_15165-0003/combined.BIN SDU.ROM cd ..

### インストールテープの準備

cd tapes wget http://bitsavers.trailing-edge.com/bits/LMI/lambda/00_install.tap wget http://bitsavers.trailing-edge.com/bits/LMI/lambda/02_system.tap cd ..

### ディスクの準備

fallocate -l 1gib disks/disk.img

### 初期化ファイルの準備

echo "sdu_switch 0" > ld.conf

あとは大体READMEに書いている通りに進めばセットアップは完了する

インストール時のはまりどころ

一番最初は、ld.confをどう書くのか最初は良く分からないのでデフォルト値を利用することになる。
はまりそうなデフォルト値は、ディスクのdisks/disk.img位かなと思う。

また、イーサネットデバイスを利用するのにroot権限が必要だが、

sudo modprobe tun 
sudo tunctl -t ldtap -u ユーザー

等とするか、root権限で実行することになると思う。 Xの画面が開かない時は.Xauthorityをrootのホームディレクトリにコピーする等適当に凌げば良いかもしれない。

170423130139

起動できたら、とりあえず、ユーザーのホームディレクトリでも作成してみる。

(fs:create-directory "mc")

そしてログイン(特に何も起きないが……)

(login 'mc)

初期化ファイルは、ユーザーディレクトリのlispm.initらしい(CADRと同じ)

170423130315

バックアップテープからのリストア

F12でテープを02_system.tapに切り替えて、System B(F1を押してからB)でバックアップユーティリティを起動。 中程のメニューのModesからRETRIVEを選んで、上のCommandsメニューからRESTORE-FILESを選択すると、読み込みと展開が始まる。結構長い。

lmi-lambda-restore

結び

とりあえず速報という感じでまとめてみた。
ホストとのファイル共有や、ネットワーク周りがまだ良く分からないが、ホストとファイルをやりとりできるようになると非常に捗るので方法を探りたい。

いやー、LambdaDeltaの今後の進展が楽しみだ!


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Notes on the Programming Language LISP: The Macro Facility

Posted 2017-04-20 15:22:38 GMT

今回は、最近bitsaversにアップされた、Bernard Greenberg氏が1976-78年に書いたLispの入門の手引きNotes on the Programming Language LISPのマクロの章を読む。

Bernerd Greenberg(BSG)氏といえば、Multics Emacsの作者として有名だが、Lispハッカーとしても有名。
そんなBSG氏がMITの学生向けに書いた手引きらしい。
書かれた時期は、BSG氏がまさにMultics Emacsを開発していた頃にあたる。

The Macro Facility

まず、Maclispで魅力的な機能の一つとしてマクロを紹介。
言語を拡張できることを説明し、具体例としては、(setq list (cons item list))のような頻出するものをユーザーレベルでpushとしてまとめられることを示す。
マクロのメリットとして、コンパイル時に展開することによる効率の高さと拡張性について触れ、個々のマクロはLispコンパイラの断片ともいえる、と述べる。 また、Lispでマクロがこれ程便利なのは、LispコードはLispデータであるということを説明し、PL/IやFORTRANではこれ程簡単に言語を拡張できないことを挙げる。
さらに、括弧で表現することに目を瞑れば、どんな拡張でもマクロ→素のLispという変換を使って書くことが可能ということと、その拡張した表現も効率よくコンパイルされうるということがマクロの最大の魅力としている。

最後にDavid Moon氏の

"Functions compute, macros translate."

という言葉を引用して締める。含蓄があるような無いような……。

結び

割と扇情的なところもあり、10年位Paul Graham氏の先取をしている所もあるなあと感じた。
全体的に面白い読み物ではないだろうか。


HTML generated by 3bmd in LispWorks 7.0.0

CADR System 78.52 其の二

Posted 2017-04-08 12:16:32 GMT

CADR System 78.52 — #:g1でams氏のプロジェクトを紹介したが、本プロジェクトは、LM-3/mit-cadrだそうで、ams氏のものは色々実験的なものだったらしい。

LM-3/mit-cadrの方を試してみたら、こちらはLinux版の場合は、これまでのようにSDLを利用するようになっていて、キーボード・マウスともに問題なく機能するようだ。

なお、usim -r ../../lispという風に明示的にシステムルートを指定する必要があるのは、現状こちらも同じらしい


HTML generated by 3bmd in LispWorks 7.0.0

Daniel Bobrow 氏と Lisp-2

Posted 2017-04-05 15:24:36 GMT

Daniel Bobrow 氏が先日3月20日に亡くなった。

氏はLISP創世記より活躍され、BBN-LISP・INTERLISPと対話型システムに於いて多大な功績を残し、Common LispにもLOOPSから始まるXerox系オブジェクト指向システムの導入に於て多大な影響を与えた人物であった。

私がDaniel Bobrow 氏で思い出すのは、氏がCommon Lispでお馴染のLisp-2の創始者ということで、昔にこのブログで書いたことがあった。
話によれば、Lisp-2は1965年あたりにBBN-LISPに実装されたのが始まりらしい。

上の記事ではnet.lang.lispを引用しているが、元の議論はMITのlisp-forumメーリングリストで行なわれていたことだったようだ。

Common Lispができつつある1983年の時点でLisp-2は古いデザインという雰囲気だが、2017年の今でも使われているというのは感慨深い。

小ネタをもう一つ、ClassicCmpのメーリングリストのBobrow氏を偲ぶスレッドで、INTERLISPの表紙について語られていた

IIRC, sometime during the project, BBNLISP was renamed INTERLISP.  I still
have the wonderful manual, with the great artwork on the cover.  Warren
Teitelman (the author) doesn't have his name on the cover.  But, the bottom
portion has a guy is operating a meat grinder, with the input being the
letters of "reference manual" in random order, and the output being
"reference manual".  Danny explained that Warren Teitelman hadn't gotten
the joke :)

Danny was funny, quick witted, friendly ... RIP.

interlisp-meat-grinder

マニュアルをひっくりかえして表紙の絵の作者を探したが、どこにも載っていなかった。作者はBobrow氏だったのだろうか。

混沌としたものをミンチにしたものがマニュアルというジョークは、CLtL2の索引のジョークを思い出す。

ちなみに、GLSとBobrow氏というと、Bobrow Stacksともいわれる Spaghetti stacksに対して、GLSは、Macaroni is better than spaghettiなんてものも書いているらしい。


HTML generated by 3bmd in LispWorks 7.0.0

CADR System 78.52

Posted 2017-04-02 07:06:34 GMT

CADRのエミュレータというとBrad Parker氏のものが有名だが、それを下敷にして色々整理したものをみつけた。

まとめているのはMIT Lispマシン(LispM)界隈では良く見掛けるams氏。

利用方法

GitHubからcloneしてmakeするだけ

git clone git@github.com:ams/mit-cadr.git

cd mit-cadr/emulator/usim

make

そして、起動
※なお、disk.imgに誤ってか意図してかコロンが末尾に付くのでdisk.imgリネームする。(そのうち直ると思われる)

./usim

自動でホストと通信してファイルも読み込んでくれるらしい。
色々試してみたが、現状では、

./usim -r ../../lisp

とLispMのルートディレクトリを明示した方が良いらしい。 (M-.でのソースコードジャンプ等で間違いがない)

(login 'foo)

とすると、ユーザーfooのホームディレクトリの初期化ファイル:lispm.initを読み込むので、ここに初期化設定を書く。
さらに、現状でソースコードジャンプを上手く機能するようにするには、初期化ファイルに、

(si:set-sys-host "server" ':unix 0404 "...//mit-cadr//lisp//")

"...//mit-cadr//lisp//"はファイルのパス

と明示すると良いようだ(なお、伝統的なMACLISPでは、/がエスケープ文字なので二重に記述する)

以下、Parker氏の配布物と比べて違っているところを列挙

  • ディスクはホストのものを利用するらしい
  • ホストのユーザーと上手く連系する
  • 時刻設定がいらない
  • chaosdserverを実行しなくてもホストのファイルが読み書きできる

なお、現状、SDLを使わず、X11を使うようにしているので、マウスやキーボード操作がLinuxでは若干上手く行ってないように思われる

また、Parker氏の配布物は、System 78.48だが、こちらでは、System 78.52と若干新しい。
記録によれば、System 78は、大体1981年末から1982年あたりのシステムで、Chinualでいうと4th Editionあたりの仕様になる。

cadr78.52

結び

MIT CADRは、最終版のSystem 99まであり、これがCommon Lisp対応となっているので、これが使えるようになると良いなあと以前から思っている。
現状、ディスクイメージはあるようなのだが、マイクロコード等細かいものが揃っていないようだ。

今後もMIT CADRエミュレータの新しい動向に注目していきたい。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: プログラミングGauche — 18章 構文の拡張

Posted 2017-03-26 14:36:19 GMT

今回は、プログラミングGaucheのマクロの箇所を読む。
マクロについての解説は、

  • 18章 構文の拡張
  • 21.7章 マクロの調査

の二つ。後者はデバッグについて書かれている。

18章 構文の拡張

構文の拡張ということで、拡張する前の構文とは、という所から説明が始まっている。
Schemeらしくプリミティブを積み重ねていくのだという説明が続くがやや冗長な印象。

次にパターンマッチによるマクロとして、syntax-rulesマクロの解説がされる。
そして、マクロの健全性について解説され、マクロの利用の仕方について解説。

最後にsyntax-rulesでは扱いが面倒な、意図的な変数名捕捉や識別子の作成について解説し、それらを手軽に扱える伝統的なマクロ方式を解説する。
2008年の本なので、手続的に書けるマクロシステムとしては、define-macroのみだったようだ。

21.7章 マクロの調査

この章はデバッグの章なのでマクロを展開してデバッグしていく方法を解説している。
「マクロは最もデバッグの難しいプログラムです」と開始されるが、その根拠についての説明は特に無いようだ。
個人的には、Schemeの場合、緩い型なのに高階関数を何重にも積み重ねるスタイルが多用されがちなので、そちらの方が何倍もデバッグが難しいと感じるが……。

macroexpandを利用してマクロを展開しデバッグしていく方法を示す。
また、モジュールを跨いだ場合の問題点についても解説。

結び

プログラミングGaucheが出版されてから早9年だが、基本から実践的な所まで幅広く解説されているので、Schemeで何かしようと思っている人には今でもとても有用な本だと思う。
ただ、Gaucheの細かい所の仕様は結構変っているので、アップデートされたら良いなとは思う。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Common Lispプログラミング — 9.3 マクロ

Posted 2017-03-22 21:27:54 GMT

今回は、Common Lispプログラミングのマクロの箇所を読む。
著者のRodney A. Brooks氏は、今ではiRobotの設立者として有名。
サブサンプション・アーキテクチャでも有名で、Lisp界隈でもLucid社の設立者でありコンパイラ作者でもあった。

原書の出版は、1985年でCLtL1時代の本。
前年に発表されたCommon Lispの入門書としては最初期のものなので、他の書籍から引用されることも多かったようだ。

9.3 マクロ

最初にマクロの役割をざっと説明し、順に使い方各種を解説していく。
最初は、コードを分かり易くするためのもので、リストのアクセサとしてcadrcaddrのようなものを使っているものをfoo-namefoo-color等、プログラムの文脈的に意味のあるものにするのにマクロを使う。

次にletや、if等、制御構文系のマクロの作成方法について解説し、よりテンプレート的に書き易い道具としてバッククォートを紹介する。

そして、マクロの意図しない変数捕捉や多重評価の問題について解説してあり、マクロについて一通りの解説はされている。

気になった所

gensymの説明がちょっと間違っていたりするが誤植かもしれない(0引数と説明されているところ)

結び

お題が小分けになっているが、それぞれの説明の後には、練習問題が付いていて、なかなか良い構成だと思った。
章末には大き目の問題が付いている。
練習問題の作りも丁寧。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Lispプログラミング入門 — 11・4 マクロ展開

Posted 2017-03-20 13:06:24 GMT

今回は、Lispプログラミング入門のマクロの箇所を読む。
同名の書籍があるが、白川洋充著の本で、1989年出版。
対象のLisp方言はCommon Lispで、CLtL1に拠っている。ちなみに用いられる処理系は、Macintosh Allegro Common Lisp 1.2.2とのこと。
ifのインデントなど、MACLっぽいなとは思う。

11・4 マクロ展開

マクロの解説は、マップ関数の章にまとめられており、大まかにはリスト処理の仲間としているらしい。
コード生成については、前の章で触れられているので、マクロ展開として焦点を絞っているらしい。

主にlambdaからletを作ることを説明して終了。
defmacro引数の分離destructuring機能とバッククォートについて軽く触れるのみ。

章末の問題では、UCI LISPのrepeatとINTERLISPのforの繰り返し構文を作成せよとなっている。
INTERLISPのforは真面目に作ると大変だが、まあ、格好だけ似せれば良いのだろう。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: 入門LISP — 2.16マクロ

Posted 2017-03-19 15:36:37 GMT

今回は、入門LISP―対話型アプローチで学ぶのマクロの章を読む。

原書の出版は、1986年でCommon Lispも出ていると思うが、主にFranz Lispをターゲットとして解説が進む。

本書では、Lispのプログラミングスタイルを、適用型のプログラミングスタイルと、非適用型のプログラミングスタイルに分けていて、最初は、関数呼び出しとその返り値のみを使う適用型で進み、応用として、副作用を用いる非適用型を解説する流れになっているのが、なかなか面白い構成だと思う。

Franz Lispを主なターゲットとしているので、マクロの解説でも、defdefundmの中からタイプが少ないという理由からdmを選んで説明。
ちなみに、Franz Lispは色々な方言を積極的に取り込んでいるので、独自のdef、MACLISP風のdefun、Stanford LISP 1.6系のdmがデフォルトで利用可能。

マクロの説明の前にFEXPRで特殊形式を作成する方法は解説しているので、それをマクロに置き換えていくのが主な内容となっている。

章末に問題があるが、そこそこの分量がありつつ難易度も手頃なので全部解いてみると良いだろう。
ちなみに、最後の問題は、訳の機微が微妙だと思った(2.16 7)
恐らく、FEXPRでevalを明示的に呼び出していたのをマクロを使うことによってevalを使わないようにせよ、という所だと思う。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: LISP — 8 FEXPRとMACROの定義

Posted 2017-03-17 10:11:22 GMT

今回は、LISPのマクロの箇所を読む。
本書は、WinstonのLisp本の第一版で、原書の出版は、1981年。
安く売られていることが多いが、Common Lispではなく、MACLISPなので注意。
本章の内容もCommon Lispには存在しないFEXPRについて解説がある。

ちなみにこの本は、訳文の文体が独特なことで有名。

8 FEXPRとMACROの定義

まず、FEXPRの解説から。大まかにいって不定長引数と引数を評価しない関数であることが説明される。
関数では定義できない代表的なものとして、ifをFEXPRで定義してみせる。
しかし、FEXPRが内部でevalを呼ぶときに注意しないと変数名の競合を引き起してしまうことを説明。
この問題はマクロでエレガントに解決できるとしている(マクロも似たような問題があるが……)

そしてマクロの解説。
バッククォートを使わない(使えない?)ためsubstを使って雛形を加工していく方法が紹介されている。これはこれで面白いかもしれない。
ただ間違った置換が起きないように注意する必要はあるかも。

(defun if macro (x)
  (subst (cadddr x)
         '$$else
         (subst (caddr x)
                '$$then
                (subst (cadr x)
                       '$$pred
                       '(cond ($$pred $$then)
                              ('T $$else))))))

練習問題も定番のものが多いがそこそこ面白いので全部解いてみると良いだろう。

結び

1981年の本にしては、ちょっとLISP的な内容が古いような気がするが、1977年に出版されたArtificial IntelligenceのLISPの部分を切り出したものが原書ということなので、しょうがないのかもしれない。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: LISP 1.5 PRIMER — 19.6 MACROS

Posted 2017-03-12 06:26:54 GMT

今回は、LISP 1.5 PRIMERのマクロの箇所を読む。
出版は、1967年で丁度半世紀前の本。
出版されたLISPの入門書としては最初期のものではないだろうか。もしかしたら最初の本なのかもしれない。

邦訳も1970年にLISP入門として出版されている。

自分が知る限りLISP入門以前にLISPの本は出版されていないようなので、日本語のLISP入門書はこれが初ではないだろうか。

表紙のタイトルがS式になっていて、こういう意匠もかなり初期の試みだったのではないだろうか。
なんとなく当時から括弧が好きだった人は好きだったんだなという感を抱いてしまう。

(LISP 1.5 PRIMER
  (BY
    (CLARK WEISSMAN)))

19 LIST STRUCTURES, PROPERTY LISTS, AND MACROS

面白いのが、リスト操作とマクロが一緒にされているところ。
実際リスト操作なのだが、バッククォートが発明されてからは、マクロを書くのはテンプレート操作という印象が強くなったように思う。
Schemeのsyntax-rulesに至ってはテンプレート言語だし。

さて、まず、特殊形式を実現するのにコンパイル時に展開するマクロが使えるということが解説される。
この辺りは、Timothy Hart氏がマクロをLISP 1.5に導入した経緯を踏まえているように思うが、この本では、特殊形式の役割は可変長引数と引数を評価しないこととしていて、マクロでそれらをどう処理できるのかを解説する。

なお、マクロの定義フォームは、MACROで、

MACRO (( (name (lambda (form) ....))
         (name (lambda (form) ....))  ))

のようになる。
ちなみに、defmacroでいうと&whole引数のようにフォーム全体が渡されてくる。

課題の一つである、可変長引数の処理については、コンパイル時にplusを固定引数の下請けである固定引数の*plusの組み合わせに展開してしまう手法を説明。

LISP 1.5では、マクロでの再帰的定義はできなかったようなので、展開用の*expandという関数を定義して、これがplus*plusに再帰的に展開する。
どうも展開用の関数を定義するというのは当時メジャーなマクロ書法だったようで1960年代後半のマニュアルなどにも頻繁に紹介されている。
展開用の関数がやっていることは、リスト操作の極みなので、マクロがリスト操作の章にあるのも分かる気はする。

また、もう一つの課題である、クォートの方は、csetという大域定数を定義する関数を、cset+quotecsetqにする例を取り上げる。

章末には、マクロの問題も、6つ程あるが面白いので解いてみることをお勧めする。
defmacroとバッククォートは使わない縛りだとなお面白い。

結び

日本の1970年代後半位の書籍だとマクロについては軽く触れられているのみだが、米国では1960年代からコンパイラと合せて紹介していることが多い。
日本ではコンパイラについてはあまり注目されなかったのだろうか。その辺が少し不思議。

ちなみに、1965年より前の文献になるとマクロは、「Timothy Hartのマクロ機構」という感じで、LISPにマクロを始めて導入したHart氏の名前と共に紹介されることが多いようだ。

なお、本書籍は、PDFがSoftware Preservation Groupからダウンロード可能なので一度眺めてみてもらいたい。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Dylan Programming — 6.4 Macros

Posted 2017-03-10 15:14:00 GMT

今回は、Dylan Programmingのマクロの箇所を読む。

Dylan Programmingは、1996年の出版で、主な対象としている処理系は、Harlequin Dylan。

当初、Harlequin Dylanは、DylanWorksと呼ばれていたらしいが、1.0がリリースされる前に名称変更になったらしい。

Harlequin Dylanは、1998年辺りに正式リリースがされ、1999年にEnterprise Editionがリリースされた後、色々あって、Functional Objectsという会社が扱うことになり、さらに色々あり、Open DylanとしてOSS化された。

以降、現在に至るまで、Open Dylanとして開発が活発になったり停滞したりしている。

本書のPDFは、OpenDylan.orgからダウンロード可能

6.4 Macros

DylanのマクロはLispのように言語を拡張することを目的としていて、扱いも用意であることが解説される。
なお、Dylanの競合は、C++やCなので、Cのマクロとの比較が随時差し挟まれる。

構文マクロには、パタンマッチ・テンプレート方式と、構文を操作する手続き型マクロがあるが、Dylanの場合は、テンプレート方式であることが説明される。
後々、手続き型マクロもサポートする予定だったらしい。なんと、知らなかった。

簡単なパタンマッチでマクロを書く例が示され、次にConstraintsについて説明がある。
Constraintsとは、?foo:expressionのようなパタン変数の後ろの部分で、構文に型が付いているようなもの。
Common Lispでいう&restのようなものは、?var:bodyと書けたりする。

また、サブパタンを書くことも可能で、見通しよく分割できることが示される。
マクロ内で識別子を生成する方法も解説。

次に、マクロの衛生性について解説され、識別子の意図しない捕捉等が起きないことが示される。
衛生性は意図的に破ることが可能で、?=を利用すれば利用される場所の識別子が利用できる。

次に、サブパタンを利用しては書けない例として、生成した識別子がサブパタンでは利用できない例を挙げ、補助マクロを利用する方法を解説する。

結び

最近、中置言語にLisp風のマクロを導入する例が多いが、最近の言語と比べてもDylanのマクロシステムは洗練されているように思える。
Dylanには、Common Lispの開発者が多く参加しているが、構文マクロを普通に使う人達が設計したことがシステムの洗練に大きく寄与したのではないだろうか。

Common Lispを知っている人からすると、Dylanは、まるで中置のCommon Lispで、Common Lispの精神もかなり継承しているのであるが、ブレイクしてくれなかったのが悔まれる。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: プログラマのためのCOMMON LISPガイド — 11 マクロ

Posted 2017-03-07 18:48:22 GMT

今回は、プログラマのためのCOMMON LISPガイドのマクロの章を読む。

1988年邦訳で、原書であるA Programmer's Guide to Common Lispも1987年出版で、CLtL1な時代の本。

「プログラマのための」とあるように、何かしらの言語の経験は前提にしている。

11 マクロ

まず、マクロの効用について説明があり、次に簡単な定義の方法とバッククォートの説明がある。
次に一歩進んで、マクロを書くマクロの説明があり、ネストするバッククォートの書き方(,',の使い方等)が親切に解説されている。
そして、マクロのλリストのデストラクト分配機能について解説がある。
これら一歩進んだ機能を解説する題材として簡単なdefstructを作って行くが、defstructを作るには一通りの知識が必要となるので確かに手頃な題材だと感じた。

章末の練習問題も手頃。後半は、本章で解説した自作のdefstruct:conc-nameや、:constructorを使えるように拡張する等、実際的な問題で面白く為になる。
なお、Common Lispの仕様に忠実に:constructorをサポートするとなると結構大変だが、巻末の解答を眺めるとさすがにそこまでは要求していないらしく、名前を変更する位で良いようだ。

また、:conc-nameや、:constructorサポートするにあたって構造体名の所はリストだけのサポートで良いらしい。
デストラクト分配を使えとあるので、destructuring-bindなし(CLtL1の仕様では存在しない)でどうやるのか色々考えてしまった。
まあ、下請けのマクロを定義するだけで良いのだが……。


HTML generated by 3bmd in LispWorks 7.0.0

LispのloopやformatはUNIXでいうsedやawkである

Posted 2017-03-04 18:38:29 GMT

Common Lispのloopformatは言語内DSLの代表例と考えられていて、その機能の多さからLisperの破天荒さを象徴するものと捉える向きも多い。
しかし、これらの言語内DSLは別にLispらしさの象徴でもないし、寧ろ原理主義者からは昔から嫌われているものの代表例でもある。

formatや繰り返し構文はFortranに由来するが、やりたいことを一つの関数に詰め込んでしまったため、ごちゃごちゃすることになってしまった。

loopは、INTERLISPのCLISPというユーザーフレンドリーな機構の繰り返し機構のforが先祖で、他の言語に親しんだ人のために中置で書ける繰り返し構文をLisp内で実現したものに由来する。

Common Lispのloopformatの内情を知っていれば、Unixのsed、awkのようにシェルからは独立した処理系や、その他オプションがどんどん増える各種コマンドについての歓迎と批判が、非常に良く似た構図であることに気付くと思う。

Unix方面では、原理主義として、一つのコマンドは一つのことしかせず、それをパイプで組み合せることを徹底したPlan9文化(またはdjb氏のユーティリティ群)があり、awkのようなものがさらに発達したPerlのようなものもあり、便利なら良いじゃんということでオプションが沢山付いたGNUのユーティリティがある。

Lisp方面で言えば、Plan9はScheme文化、formatloopのようなものは、sedawkのようなもので、便利なら良いじゃんというのは、大多数のCommon Lispユーザーの立ち位置、という感じになる。 Gaucheのように全体のバランスを考えつつ便利さも失なわないように整えている環境もある。

sedawkがUnixの対話環境の象徴かといえば、そんなことはなく、パイプでコマンドを組み合せていくスタイルこそがUnixの対話環境らしさだろう。
そのような基本があった上で、便利さを追求したり統一性を重視していると思う。

Lisp方面も同じで、formatloopがLispの象徴ということはないのである。

単一の関数やコマンドに詰め込むことの批判として、徹底して小さい部品を組み合せるアプローチも多数出ている。
formatや、loopであれば、コンビネータに機能を分解して、それを組み合せる等々のアプローチがあるし、Plan9ではコマンドは本当に一つのことしかしない。
面白いのがこれらのアプローチが絶対的に使い易いかというと、そうとも言い切れない所である。細かく分割した部品が他のドメインでも上手く使えたりして一石二鳥かというと、そうでもなかったりする。

CLerは割合に現実的なので、loopformatも全機能を熟知した上で使うということもなく、適度なイディオムを利用しているのみである。
また、設計に於いても極力機能をアトミックに分割しようということも無く中庸といった所だ。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Anatomy of LISP — 3.12 Special Forms and Macros

Posted 2017-03-04 17:35:50 GMT

今回は、Anatomy of LISP / John Allenのマクロの箇所を読む。

LISPの構造として邦訳もある。
邦訳は入手できていないので詳細は不明だが、どうも邦訳の方は端折っている部分が多いらしい。

原著の初版は1978年で、LISP処理系の内部を解説した名著として知られている。
マクロについては、3章のEVALUATION OF LISP EXPRESSIONSの評価器から眺めた解説と、6 6章のTHE DYNAMIC STRUCTURE OF LISPのコンパイラから眺めた解説がある。

3.12 Special Forms and Macros

eval(インタプリタ)から眺めた特殊形式とマクロの解説。
andを実現するのに、まず、関数ではandが作れないことを解説し、evalを拡張する方法を示す(evandを作成)。
次に、同様のことを実現するのにマクロというものを評価器に導入することを説明。

また、可変長引数を実現するのにマクロを使うという手法を解説する。
可変長引数のplusは固定引数(2つ)の*plusに展開されるというもので、マクロそのものを用いてはいないが、現在もCommon Lisp内部ではコンパイラがインライン展開etc.で行なっていることと同じ。

マクロの種類として

  1. Textual Substitusion
  2. Syntax Macro
  3. Computational Macro

三つを取り上げ、単なる文字列の置き換え→構文の置換→サブツリーの変換と強力になることを説明するが、Computational Macroであっても扱うサブツリーに必要な情報が含まれている場合にしか有効でなく、サブツリー以外の場所の情報を必要とする場合や、サブツリーに追加情報が必要な場合にも、うまく立ち回ることができていないことを解説する。

6.18 Macros and Special Forms

コンパイラの方からマクロを眺めた解説。
大体インタプリタの方と同じだが、コンパイラからすると扱いが厄介な特殊形式を排除できることと、マクロはコンパイル時に計算してしまえる部分を多くすることが可能で効率的な処理に寄与することが解説されている。
また、特殊形式が増えるとevalが複雑で重くなるという解説もある。

結び

古代のLISPでは、マクロを説明する際には、FEXPRであったり特殊形式との比較が出てくることが多い。

マクロには、処理系を改造しなければいけない所をユーザーレベルで拡張できるところ増やす、という効用があった。
また、コンパイル時計算との親和性も高く、FEXPRで実現できることも大抵置き換えることが可能なので、FEXPRのようなコンパイルが難しい(と当時は考えられていた)形式も置き換えることとなった。

処理系内部をユーザーに開放しつつ効率が良い道具を提供するというのもLISP処理系の一つの核となる歴史の流れかなあと思う(マクロ、MOP、etc.)


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Realm of Racket — ).3 Racket Is a Metaprogramming Language

Posted 2017-03-03 20:27:07 GMT

今回は、Realm of Racketのマクロの箇所を読む。
Realm of RacketはLand of LispのRacket版のような感じで、Conrad Barski氏も著者の一人。
とはいえLand of Lisp程個性が強くないので物足りない人もいるかもしれない。

マクロの解説は、最後の章(Good-Bye)で

  • ).1 Run Racket Run
  • ).2 Racket Is a Programming Language
  • ).3 Racket Is a Metaprogramming Language
  • ).4 Racket Is a Programming-Language Programming Language

という風にメタプログラミング度を増して解説していくうちの3番目にあたる。

).3 Racket Is a Metaprogramming Language

Racketは他のLisp方言と比べても一番強力かつ表現力が高いという前置きがある。
とはいえ、この章ではそこまで詳しくは解説せず、言語の構文を拡張する方法として簡単なマクロを解説するのみ。
主にRacketで用いられるdefine-syntaxsyntax-rulesを合体したような、define-syntax-ruleを利用するが、それ程つっこんだ解説はしない。

(define-syntax-rule
  (my-and a b)
  (if a b #f))

衛生マクロなので変数捕捉等については特に気にする必要はないが、一応わざと大域変数とマクロ内で利用している変数の名前を衝突させてみたりしても上手く動きますねという解説がある。

次に、パタンマッチの書式の簡単な解説として、lambdaに展開されるletを作成しつつ説明する位で終了。

).4 Racket Is a Programming-Language Programming Language

マクロの応用として、DSLを作ってみせる内容だが、Racketのモジュールと#langの仕組みを使ってデバッグプリント付きの言語から、Racketに遅延評価を導入してみせたりする。

遅延評価の導入については、Racketで関数適用は、%#appを利用していて、これを差し換えることによって実現する。

この機構のお蔭でevalに手軽にフックを掛けるようなことができ、マクロともリーダーマクロとも違う第三の拡張方法という感じ。

ちなみに、evalにフックを掛ける機構は、Common Lisp系でも*evalhook**applyhook*というものが存在したが、インタプリタ動作とコンパイラ動作の整合性向上のためにANSI Common Lispでは廃止になっている。

結び

Racketが強力というのは良く耳にはするが、あまり触ったことはなかった。
メタプログラミングについては、確かにかなり洗練されていて強力そうだなと感じたので、もうちょっと遊んだりしてみたい。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Loving Common Lisp — Defining Common Lisp Macros

Posted 2017-03-01 11:48:36 GMT

今回は、Loving Common Lisp, or the Savvy Programmer's Secret Weaponのマクロの章を読む。
2013年に第一版、2016年に第三版が出ている電子書籍。
143頁でKindle版が現在¥583。

最近の書籍だけにQuicklispの説明などもある。

著者のMark Watson氏は、三十余年年のプログラマ人生の中でLispを始め様々な言語を使ってきたが、LispではNLP自然言語処理や、VR仮想現実に取り組んできたとのこと。

1991年にCommon LispでNNニューラルネットワークNLP自然言語処理や、カオス理論を学ぶ本も出したりしている。

但し、1991年という年からも分かるように、時代が一周しているので注意。

Defining Common Lisp Macros

マクロについてはさらっと流していて、説明する内容も、Lispでは構文が拡張できることと、バッククォートの簡単な説明位で、あとは展開形の確認の仕方ということで、macroexpandの説明位。

結び

本書は、最近のCommon Lispについてざっと説明するような内容で、各項目も手頃な分量で書いてある。
最近のCommon Lisp全体を俯瞰するような目的には良いかもしれない。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: 記号処理プログラミング 2.1(b) マクロの機能

Posted 2017-02-26 18:17:43 GMT

今回は、記号処理プログラミングのマクロの箇所を読む。

本書は、岩波講座 ソフトウェア科学の第8巻で、記号処理言語としてLispとPrologの解説があり、記号処理機の説明としてELISの解説もある。
著者の後藤滋樹先生はNUE関係の方でもあるが、本書はTAO/ELISの入門書としても使えたのかもしれない。
出版は、1988年で記述はCommon Lispに合せてある。

マクロの解説は、「親言語としてのLISP」という段落の中にあり、基本的にDSL構築の手法として紹介している。
親言語とは、DSLに対してのホスト言語のこと。

Lispは拡張可能言語であることが紹介され、Fortran等との比較がある。

マクロには、マクロ文字と構文のマクロがあることが解説され、概念的な説明が終わる。

具体的な使い方は別の章で解説となるが、後の章では、ATNを用いた英語の構文解析でDSLを作成するアプローチを紹介し、これにマクロを利用する。
バッククォートやdefmacroの解説もこのDSL作成の過程で解説するようになっている。

基本的にATNのDSLを作成する必要に応じて解説しているので、マクロ機能単体で細かく説明することはないが、こういう説明も面白いと思った。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Object-Oriented Common Lisp 11 Macros

Posted 2017-02-25 20:18:30 GMT

今回は、Object-Oriented Common Lispのマクロの章を読む。
著者のStephen Slade氏はTの開発者として知られていて、Tについての著作もある。

各章の冒頭は古典からの引用で飾られていて随分知的な雰囲気がある。また語彙が豊富なので英語が苦手な自分のような人間には単語を辞書で確認しつつ読み進めるのが面倒臭い。

まず、Lispでは構文を拡張できることが説明され、repeatという繰り返し構文を作りながら解説が進む。

バッククォートの説明では、defmacroとの組み合わせの他にdeftypeにも言及されていて、これは少し珍しいと思った。

ちなみに、本書でも、Common Lisp Programming for Artificial Intelligenceのバッククォートの説明の箇所が引用されていた。

本書のマクロ作成解説で特徴的なのは、gensymを使わない所。
他の書籍では変数の捕捉問題には大抵gensymを使った対処方法が説明されているが、本書では、変数のスコープを別々にすることで対処する方法のみを説明する。具体的には、repeatのようなものは、

(defmacro repeat (n result &body body)
  `(flet ((body () ,@body)
          (result () ,result))
     (let ((n ,n))
       (do ((count n (1- count)))
           ((<= count 0) (result))
         (body)))))

(repeat 3 nil (print 'a)) ⊳ a ⊳ a ⊳ a → nil

のように本体や結果節を繰り返し構文の変数スコープの外に置くことで捕捉を防ぐ。

ただ上記のようにfletを使った場合、関数名のブロックが作られるため、

(repeat 3 nil 
  (progn (return-from body) (print 'a)))
→ nil

としてしまうと、ブロック名を捕捉できてしまうので、無名関数を使うなり関数名をgensymで生成するなりする必要があるだろう。
なお、本書では上記のような変数以外での捕捉問題の解説はない。

また、局所関数に展開する方法の他に、下請け用関数を定義するバージョンもあり(練習問題の解答で詳細が解説される)

Common Lisp Programming for Artificial Intelligenceでも紹介されていた方法だが、一歩進めた感がある。

次に、関数とマクロの比較、分配束縛、リーダーマクロと解説が進む。
リーダーマクロについては結構詳細な解説がある。

最後に進んだ話題として、リードテーブルの文字ケースの扱い、define-modify-macro、ディスパッチマクロ文字、*macroexpand-hook*について解説がある。

面白いのが、*macroexpand-hook*の解説で、traceのマクロ版を作成しつつ解説が進むが、何故かとてもボリュームがある(8頁も)。
コードが多いとはいえ、*macroexpand-hook*についてここまで詳しい本は他にないのではないだろうか。

練習問題も充実しているが、設問の意図がはっきりしないものが多く、またあまりマクロに関係無い問題もあり飛したくなる。

解答が巻末に付いているので、意味が分からない場合は、解答を見て意図を把握してから問題に取り組むと良いかなと思った。

気になった所

  • ANSI CL規格に沿ってない
  • リードテーブルの扱い
  • 処理系依存なコードが普通に出てくる
  • Common Lispの命名規約に沿ってない

1997年出版であるので、ANS沿って説明かと思いきや、どうもCLtL2相当の説明らしくspecial-form-p等が普通に出てくる。

リードテーブルの扱いというのは、(copy-readtable rt nil)すれば良いのに、無駄なユーティリティを作成しているように見える点。

処理系依存コードについては、Allegro CLに依存しているらしいが、著者はどの辺りが処理系依存かはあまり把握していなさそう。

命名規約に沿わないというのは、スペシャル変数には、*earmuff*を付けるのが規格が採用しているマナー(というか謎のバグ防止)であるのに付けない所。

結び

マクロの章しか読んではいないが、どうも本書は脱線が多いのではないだろうか。
ディスパッチマクロのディスパッチという概念を解説するのに紙テープ時代の話を引用したりとか……。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: はじめてのLisp関数型プログラミング 2.2.8 マクロ関数

Posted 2017-02-24 16:18:21 GMT

今回は、はじめてのLisp関数型プログラミングのマクロの箇所を読む。

本書はLispで関数形関数型プログラミングを学ぶのが主なのでマクロについては3頁程でさらっと流している。

対象のLisp方言は、ISLISPか、Common Lisp。
Lispのマクロは文字レベルではなく、構文レベルであること、バッククォートでテンプレート的にマクロを書く方法、評価方法がざっと書いてある。

3頁に色々詰め込んであるので、初学者にはちょっと難しいかなと思った。
これで興味を持ったら他のもっと詳しく書いてある本で勉強するのが良いと思う。

マクロでifを定義するのに、ifを使う例があったが、そういえば、ifを使わない方法があるのを思い出した。

Henry G. Baker氏のMetacircular Semantics for Common Lisp Special Formsにあるアイデア。
※記憶で書いたらシンボルの属性の使い方が逆だった。

(setf (get 'my-if 'T) 
      (lambda (then else) 
        (declare (ignore else))
        (funcall then)))

(setf (get 'my-if 'NIL) (lambda (then else) (declare (ignore then)) (funcall else)))

(defmacro my-if (pred then &optional else) `(funcall (get 'my-if (not (null ,pred))) (lambda () ,then) (lambda () ,else)))

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

(fib 30) → 832040


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: ANSI Common Lisp 10 マクロ

Posted 2017-02-21 17:43:33 GMT

今回は、ANSI Common Lisp / ポール・グレアムの10章 マクロを読む。 2003年位に本屋で立ち読みしてLispのことはさっぱり分からないが面白そうなので購入。

10 マクロ

やはり、ポール・グレアム(pg)氏の文章は面白い。
若干の主張の偏りはあるが、面白い文章でCommon Lispの入門ができるというのは素晴しい。
偏っている所が面白い所でもある。

まず、評価器の話から始めて、マクロの話につなげる。
マクロのメリットが説明されるが、ここで脚注に関数の評価について何故か細かい注釈がある。
関数と引数の評価についてだが、ANSでいうと、

あたりの話だと思われる

(progn
  (defun foo (x) (+ x 3))
  (defun bar () (setf (fdefinition 'foo) (lambda (x) (+ x 4))))
  (foo (progn (bar) 20)))

上記が23になるか、24になるかは処理系依存。
ちなみに、SBCL、GCL、Allegro CLあたりは23、LispWorks、Clozure CL、ECLは24になった。同じKCL系のGCLとECLで違っているのがちょっと不思議

更に話が逸れるが、Macro Forms as Placesの話もさらっと書いてあった。
読んだ筈だったが、すっかり忘却してしまっていたらしい……。

閑話休題。次にバッククォートの説明がされ、マクロの使い方と、マクロの設計について、またマクロ作成時に注意する点等が一通り解説される。

マクロの設計について色々語る本は少ないと思う(On LispとかLet Over Lambda等目立つ本はあるが……)。

次に、一般化参照の解説と、マクロを書くためのユーティリティを解説し、最後に、未だ開拓され尽されてはいないマクロの可能性で締める。

練習問題も考えさせるような問題で良くできていると思った。

気になった所

aifitcall-next-methodの引数省略のようなものが同列に説明されているけれど、ちょっと違うのではないかなあと思った。

結び

「マクロを書くというのは〜コンパイラを書き換えられるのとほとんど一緒である」のような言い回しにやはりセンスを感じる。

解説内容も親切で詳しいし、この章にCommon Lispのマクロを書く上で必要なことは、殆ど全部書いてある。面白いので読んだことがない人にはお勧めしたい。
また、pg氏のようにマクロのユーティリティを積み上げていく独特のスタイルについては、On Lispのコードや、ユーティリティ集、Arcのユーティリティのソースコードを読むと雰囲気がつかめる


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Common Lisp Programming for Artificial Intelligence 6.2 Macros

Posted 2017-02-20 21:03:26 GMT

今回は、Common Lisp Programming for Artificial Intelligenceのマクロの解説の箇所を読む。
1989年出版の本で、2010年にAmazonで¥550位で購入

内容は年代と題名から察せられるように、GOFAIな本で、Common Lispを解説しつつも本題はAIという本。

6.2 Macros

マクロの説明としては、それ程詳しくはないが、評価機がどのようにマクロを扱うか、という視点で説明しているのは、面白いと思った。

評価機の視点で、通常の関数とマクロを比較し、なんとなく使い方を理解させるという戦略だろうか。

それが済むと、早速バッククォートを利用した簡単なマクロの作成方法を解説し終了。

基本的にコード生成の説明に近いと思った。

マクロ書法、作成上の注意点、macroletについての説明などは無し。

気になった所

マクロのユースケースとして、高速化が挙げられているが、やはりこの時代はこういうのが多かったのだろうか。この辺りに古さを感じる。

また、練習問題で作成させるマクロが、関数として実装した方が適切な物が多く、マクロである必然性に乏しいため、あまり適切な問題ではないなあという感想。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: LISPマシン・プログラミング技法 3.6 マクロ

Posted 2017-02-19 18:01:00 GMT

今回は、LISPマシン・プログラミング技法のマクロ解説を読む。

2014年にAmazonで100円で購入。
この本は、Symbolics Lispマシンの入門書だが、Lispプログラミングについても、そこそこの分量を割いている。

3.6 マクロ

簡単にソースコードの変換の仕組みであることが説明され、マクロの利用が有効な手段となる事例として、

  1. プログラムが長い
  2. 入力がめんどくさい
  3. 間違えやすい
  4. いろんなところで用いられる

位を挙げている。

ざっくり言えば複雑さを制御する手段として説明したいらしい。

詳細については、Symbolics Common Lisp: Language Concept “Macros” を参照せよとあり、後は簡単にマクロ展開のツールの説明がされるのみ

マクロ展開のツールの説明

Lisp Listener(REPL)では、Show Expanded Lisp Codeコマンド、関数としては、(scl:mexp)が紹介されている。

Show Expanded Lisp Codeというのは非常に長いコマンドだが、実際には、s e l位を打ち込めば補完してくれるので、呼び出し自体はそれ程面倒でもない。
とはいえ、Listenerに直接入力することはあまりないと思う。

(scl:mexp)の方は、実行するとCommon Lispのinspectのような感じで入力を待つので、そこにフォームを入力する。

一番手軽なのは、やはりZmacsのコマンドで、Macro Expand Expression (c-sh-M)だろう。
SLIMEにも同様のコマンドがあるが、Lispマシンから受け継いできたコマンドともいえる。
マクロを再帰的に展開するには、Macro Expand Expression All (m-sh-M)を使う。

なお、GNU Emacsだとターミナル経由で利用されることが多かったためか、Shiftのあるなしは検出しないことが多いが、Symbolicsでは、Shiftも良く使い、マニュアルではshで表し、単体の文字は大文字で書かれる。

結び

良い機会なので、後でSymbolics Common Lisp: Language Conceptのマクロの箇所も読んでみようかなと思う。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Common LISPcraft 13. Macros / 14. Macro Characters

Posted 2017-02-18 19:38:37 GMT

今回は、Common LISPcraftのマクロの箇所を読む。
2009年位に600円位で購入。
出版は、1984年(第二版1986年)で、時代的にCommon Lispの仕様は、CLtL1。
なお、邦訳はされていない。

Common LISPcraftの前に、LISPcraftというFranz Lispを対象にした本が、同じくWilensky氏によって書かれていて、それのCommon Lisp版ということで、Common LISPcraftという題名になっている。
しかし、題名は引き継いでいるものの殆ど新規に書下したように見える。

Chap. 13 Macros

アクセサの別名を作る所から始まって、マクロ展開の解説、setf系マクロの定義の方法など、割合に網羅的に解説されている。
解説は丁寧だが、古い方言や書法の話が入ったりしているので、ちょっと古さを感じてしまう。
バッククォートや、macroletの解説はなし。

Chap. 14 Macro Characters

リーダーマクロの解説。通常のマクロ文字の解説の後、バッククォートの説明があり、次にディスパッチ・マクロ文字の解説がされる。
解説は、かなり丁寧で、これ程丁寧にリーダーマクロの基本を解説している本は無かったような気がする。

バッククォートの解説の後、13章では導入しなかったバッククォートを利用してマクロを書くことが解説される。 この辺りの流れは、どうやら前回眺めたArtificial Intelligence Programmingに影響を受けているようで、解説している内容も割と似ている(がこちらの方が簡素)。

ちなみに、13章のマクロのネタにも、Artificial Intelligence Programmingのネタが使われている。

結び

この本は基本的なスタンスとして機能は網羅的に解説するように思えた。

網羅的に解説しつつも取って付けたような感じはしないので、解説が上手いのかなあと思ったりするが、例題にCommon Lispより前の古えのLisp方言の機能を真似るようなものが多く、ちょっと古臭いのが残念。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Artificial Intelligence Programming 2nd ed. | 2. Macros and Read-Macros

Posted 2017-02-14 17:24:27 GMT

今回は、Artificial Intelligence Programming 2nd ed.を読む。 2013年にAmazonで565円位で購入。
第一版の方は、人工知能プログラミング / 日本コンピュータ協会として翻訳もされているらしいが、この本は何故かAmazonでは取り扱いがない。
第一版の方は出版が1981年ということもあるので、LispはCommon Lisp以前の方言だと思われる。

第二版は、1987年の書籍。
人工知能プログラミングの本だが、半分位はCommon Lispの解説があり、PAIPと似たような構成。というかLispの人工知能本は、第一部Common Lisp、第二部人工知能という構成が殆どで、この本もそのようになっている。

著者は、Eugene Charniak、Christpher K. Riesebeck、Drew V. McDermott、James R. Meehan各氏でLisp界隈では有名な方々。

制御構文の詳しい解説もされていない2章の時点でマクロとリーダーマクロの解説を始めるというのは、なかなか珍しいと思った。
しかも、リーダーマクロの解説が先。リーダーマクロの解説の題材もバッククォートを展開する関数を定義して、動きを観察したり、動作をいじってみたりする。
set-macro-characterの使い方なども解説される。

リーダーマクロの次に通常のマクロの解説になるが、バッククォートの詳細は理解しているので、そちらの解説はなし。
マクロ学習の題材としては、letの作成が取り上げられる。

次に、repeatというdotimesのようなマクロをお題にして、マクロ変数の捕捉問題と、多重評価問題を解説する。
この解説の中で、変数捕捉回避の方法として、gensymを使った方法とは別に、thunkを使った方法が解説される。

(defmacro repeat (n &body body)
  `(let ((thunk (lambda () ,@body))
         (cnt ,n))
     (loop 
      (when (>= 0 cnt) (return))
      (funcall thunk)
      (decf cnt))))

更に、このrepeatの場合は、下請け関数にthunkを渡すように展開してしまうことも可能であることが示される。

(defmacro repeat (n &body body)
  (repeat* ,n (lambda () ,@body)))

この辺りは類似の本には見られない解説で面白い。
一応、クロージャーで変数捕捉を回避する方は、関数呼び出しが発生するのでベタに展開するマクロより効率は落ちるという説明もされている。

下請け関数に全部任せてしまうのは、最近では、call-withスタイルとも呼ばれ、Google Common Lispスタイルガイドでも紹介されているものだが、結構昔からあるんだなあと感心

なお、thunkもインライン化されたりコンパイラが優秀ならば、ベタなマクロと同じ効率になることもあるので、一概には効率が落ちるともいえないと思う。

といっても、まあ効率重視の局面ではベタに書くのが良いのだろう(二転三転)

そして、let特殊変数スペシャル変数専用の結合束縛構文版を作成する。これは後の章で更に込み入ったものとなる(最終的にはletfのようなものになる)

気になった所

題材は面白いんだけれども、練習問題を解くのは結構難しい気がした。
機転が利く人は前提知識がなくても解けるのかもしれないが、解けなくても、自分なりに考えた上で解答を見て動作を確認すれば、へえと感心するような類の問題が多い気がする。 (なお、回答は巻末に纏まっている)


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: 実用Common Lisp | 3.2章 特殊形式: マクロ etc

Posted 2017-02-13 17:01:50 GMT

今回は、実用Common Lisp、通称PAIP。名著として名高い本のマクロの解説を読んでみる。

3.2章 特殊形式: マクロ

この本では、まず、特殊形式の仲間としてマクロを解説し、後に正確な定義に近付けているらしく、この章でマクロと特殊形式の違いが解説される。

マクロを書く順序として、

  1. 本当に必要かどうか
  2. 構文を書く(書き出す・考える)
  3. 展開形を考える
  4. 実際にdefmacroで記述する

としていて、本当に必要かどうかまず良く考え、書くとしたら既存の文法に合せるように書くべきであることが説明されている。

実際にマクロを書く例として while を作成するが、最初は、バッククォートを利用しないもので書き、次の段落(逆引用符表記法)でバッククォートを利用したテンプレート作成的な書き方を説明。

他、

  • 24.6 シーケンス関数: Once-only: マクロ論のレッスン・マクロ乱用の回避
  • 25.14 マクロに関する問題

でもマクロについての解説あり。

全体的に機能の解説をするついでにコーディングスタイルの話もすることが多いため、余計な解説と情報が多いなという印象。
特殊形式の解説の筈なのに、シーケンス関数の話も混ざっていたり、後の章の話が度々出てきたり、Common Lispの基礎を学ぶのにはこの本はあまり向いていないのではないかと思った。
しかも、コーディングスタイルについては、若干押し付けがましい。
押し付けがましい割には、Norvig先生のコード例も自身のコーディングスタイルに沿っていなかったりする。
また、示されているスタイルは昔からのオーソドックスなものというよりは、Novig先生オリジナルな成分が多目なスタイルに思える。

大著なので、最初に完成した部分と、入門者向けの部分等で統一感がないのかもしれない、と少し思った。

スタイルについては話半分で流しておいて、そこそこ書けるようになって、気になってきたら、Norvig先生がKMP氏と一緒に纏めたスタイル論があるので、別途そちらを参照した方が良いのではないかと思う。

気になった所

局所ローカルマクロ・関数によるシャドウについて

⸨25.14 マクロに関する問題⸩では、3.2章の内容をさらに細かく考察していて、局所ローカルマクロ・関数が展開された関数・マクロをシャドウしてしまう問題についても触れている。
具体的には、下記のような状況だが、

(defun last1 (list)
  (car (last list)))

(defmacro foo (list) `(last1 ,list))

(flet ((last1 (x) x)) (foo '(1 2 3 4)))(1 2 3 4)

これを防ぐには、

(defmacro foo (list)
  `(funcall ,#'last1 ,list))

(flet ((last1 (x) (print x))) (foo '(1 2 3 4))) → 4

と書く必要がある、と解説されている。
実際にはこのように書かれることは殆ど無いが、Common Lispでは、局所ローカルマクロ・関数が多用されないので、スタイル上あまり問題にならないとの説明。

,#'last1というのがそら恐しいが、大域定義を参照するのだから、単に'last1とシンボルで書いてしまえば良いじゃん、と思ってしまった。何か理由があるのだろうか。

  1. #'last1
  2. 'last1
  3. ,#'last1

と書けるが、#'last1と書いてしまっては元の木阿弥。
本書では関数は、#'を付けて書くように統一されているので、'last1ではなく、,#'last1とした、という所だろうか。
もしくは、インライン展開やコンパイラ・マクロへの配慮とか?

局所ローカルマクロ・関数のシャドウ問題への対処は実際には、上記のようにfuncallに分割するようなことはしないで、パッケージ・ロックで防ぐのが一般的だろう。

パッケージ・ロックは、規格外の機能だが大抵の処理系は装備しているので、これを使えばシャドウされたらエラーとすることができる。

たとえば、SBCLであれば、(sb-ext:lock-package "パッケージ")とするだけで良い(もしくはdefpackage:lock Tを指定)

(とはいえLispWorksのように大域的な再定義しかエラーにしてくれない処理系もある……)

記述が古いところがあるので注意

ANSI Common Lispが成立する前に出版された本故に記述が古い所があるdefine-setf-methodspecial-form-p等々)

macroletの解説がない

これだけの大著なのにmacroletの解説がない。
まあ人工知能がメインの本なのでしょうがないのかもしれない。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Common Lisp 入門 | 7章 マクロ

Posted 2017-02-12 15:01:35 GMT

今回は、Common Lisp 入門 (湯淺太一/萩谷昌己著)のマクロの章を読む。

マクロ展開関数は一般のリスト処理関数として定義できる.
すなわち,マクロ展開とはリスト処理の一種である

という明確な解説から始まって、マクロと特殊形式スペシャル・フォームの違い等を説明。実装者視点なのか細かい所の解説が多い。

組み込みマクロの解説としてsetfの解説があるが、普通のマクロよりこちらの方に力が入っている印象。
一般化変数generalized variableの説明も詳しい。

マクロの定義については、まずはバッククォートを使わないで説明が進み、局所ローカルマクロまで解説した後に、バッククォートを使った書き方を解説。
macroletの定義部内でレキシカル変数・関数が参照できないことが解説されているが、このことを解説している本は少ないと思う。

コードにすると、

(let ((x 42))
  (macrolet ((foo () x))
    (foo)))
⊳ Error: The variable x is unbound.

のようなもの。

ANSによると、

The macro-expansion functions defined by macrolet are defined in the
lexical environment in which the macrolet form appears. Declarations
and macrolet and symbol-macrolet definitions affect the local macro
definitions in a macrolet, but the consequences are undefined if the
local macro definitions reference any local variable or function
bindings that are visible in that lexical environment.

とのことで未定義動作になる。

実際試してみるとAllegro CLのインタプリタ動作では動いたりするがコンパイルするとエラー、他の処理系も軒並エラーとなる。

ふと、&environmentを使って中でマクロ展開したら良いのではないかと思い試してみた所、この方法ならどの処理系でもレキシカル変数・関数が参照できた。
しかしこれは合法なのだろうか。シンボルマクロの展開に辺りに解説が書いてありそうだが合法かどうかの記述をみつけることができなかった(乞う詳細)

(defun kou (arg)
  (let ((x arg))
    (macrolet ((x (&environment env)
                 (macroexpand 'x env)))
      (x))))

(kou 42) → 42

(defun ots (arg) (let ((x arg)) (macrolet ((x (&environment env) (symbol-macrolet ((x (macroexpand 'x env))) `(list ,x ,x ,x)))) (x))))

(ots 42)(42 42 42)

(defun hei (arg) (let ((x arg)) (flet ((hei () x)) (macrolet ((x (&environment env) (macroexpand '(hei) env))) (x)))))

(hei 42) → 42

(defun tei (arg) (let ((x arg)) (flet ((tei () x)) (macrolet ((x (&environment env) (macroexpand '(funcall #'tei) env))) (x)))))

(tei 42) → 42

注意点

CLtL1時代の本なので、special-operator-pspecial-form-pだったりする。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: Common Lisp & Artificial Intelligence | Chap. 5 Macros

Posted 2017-02-11 18:25:51 GMT

Lisp本を沢山積読してしまっている。折角なのでこの状況のメリットを考えてみたが、一つのトピックについて色々な本を串刺しに読むというのは積読ならではではないかなと思ったので、とりあえずマクロについて串刺しに読んでみようと思う。

この本について

Common Lisp & Artificial Intelligence / Patrick R. Harrison著は、2009年にAmazonで1円で購入。
Common Lispと人工知能というタイトルの本は沢山あるが、その中の一冊という感じ。
240頁程の内容のうち、Common Lispについては150頁程割いている。

Chap. 5 Macros

この本では、マクロの利用形態を5つに大別して解説しており、

  1. ばらつきと詳細を隠蔽し抽象化するため
  2. 文法を単純化するため
  3. 大域的な副作用フォームを作成するため
  4. 引数をクォートするため
  5. 効率的にコンパイルするため

の五つを軸に据え、それぞれに具体的な例を挙げて解説が進む。

解説する例題で、MACLISPで良く使われていたユーティリティをCommon Lispで再現する例が多いので、著者はMACLISPに親しんだ人なのかもしれない。

上記のうち、大域的な副作用フォームを作成する、というのは大域的というより、定義するフォームの外側に影響を及ぼす、という意味らしい。

マクロの解説の後、リーダーマクロの解説となるが、ここでもMACLISPのリード時マクロ展開#%を再現するような例を用いて解説している。

章の最後には問題が九つ程あるので解いてみたが、中々面白い問題かなと思った。

気になった所

マクロを作る際に問題になる変数のキャプチャ問題をFUNARG問題として解説しているところがちょっと気になった。
確かにFUNARGの変種と考えられるようなそうでもないような。
とはいえ、マクロは関数引数になるわけでもないしFUNARGとは無縁のような。

また、コード例の中のassertの構文が間違っている。


HTML generated by 3bmd in LispWorks 7.0.0

Lispのぶら下がり括弧嫌ですね

Posted 2017-02-11 15:30:17 GMT

Lispのぶら下がり括弧嫌ですね。
コッカをぶら下げてしまう人は、何故ぶら下げてしまうのか。

恐らく、括弧の対応が取れないので、行で対応を取っているのでしょう。
Lisp用のエディタを使っていないからだと思いますが、そこで行で対応を担保しつつ括弧も揃える方法を考案しました。

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

どうでしょうこれ。

ふざけた感じではありますが、eval-whenだと案外実用できるかも。

⏜
eval-when (:compile-toplevel :load-toplevel :execute)

(defun foo (x) x)

(defun bar (x) x)

(defun baz (x) x)

設定方法(Common Lisp)

(set-macro-character #\⏜
                     (lambda (srm chr)
                       (declare (ignore chr))
                       (read-delimited-list #\⏝
                                            srm
                                            T)))

(set-syntax-from-char #\⏝ #\))


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: LISP 原書第3版(I) | 12章 マクロ

Posted 2017-02-08 18:11:33 GMT

Lisp本積読解消今回はLISP 原書第3版(I)
12章 マクロ を読むことにする

最初にマクロの引数の評価について説明があり、その後にバッククォートを使い、テンプレートを埋める方式で式を組み立てる方法を解説する。

それ程込み入った解説はないが、問題が実践的で面白いのでこの章の問題が解ければ実際に作成するマクロの九割以上はすんなり書けることと思う。

また、13章の構造体の解説にもあったが、マクロもまとめて別ファイルに分けようとのこと。
理由としては、マクロは先に読み込ませたいことが殆どだからとのことだが、マクロ展開に利用する補助関数をどう扱うかについては言及がない。

Lisp Style and Designにはファイル分けについて、評価順でファイル分けをし過ぎると、意味単位のモジュール分割が阻害され、意図が良く分からないものになりがちという指摘があるが、何事も程々が良いだろう。


HTML generated by 3bmd in LispWorks 7.0.0

Lispは関数形言語

Posted 2017-02-07 06:18:52 GMT

JIS X 0007:2001の07.01.20によると、Lispは関数形言語。
「形」に注目。

07.01.20 関数形言語(functional language)

関数呼出しだけを使用して計算機システムを稼働させる
ことによって,達成されるものを記述する手段を提供す
るプログラム言語。

例 FORTH, LISP, ML, Miranda, PostScript

JIS X 0007:2001 の元になったのは、ISO/IEC 2382-7:2000らしいが、ISO/IECでも同様。

functional language:
    A programming language that provides the means to state what is to be achieved by the actions of a data processing system exclusively through the use of function calls. For example, FORTH, LISP, ML, Miranda, Postscript. Synonymous with functional programming language. Contrast with imperative language.

ちなみに、Lisp関係でで良く使われている用語の“binding”は、“束縛”ではなく“結合”らしい。 結合なら、「変数が値に結合される」でも、「変数を値に結合する」でもどっちでも良さそう。


HTML generated by 3bmd in LispWorks 7.0.0

Lucid CLとMCLのメーリングリストを発掘

Posted 2017-02-06 23:09:59 GMT

Saildartを探索していたら、偶然Lucid CLのメーリングリストの記録を発見。
Lucid CLのユーザーグループって存在しなかったのかなあと不思議に思っていたので、やっぱりあったかという思いつつも、見付かったとはいえ、いまいちぱっとしない内容だった。とはいえ貴重。

Lucid CLは主にOEM提供されていて、一番メジャーなものでは、Sunのアーキテクチャで稼動する Sun Common Lisp がある。
他にも、IBM・HP・Appolo・DEC等々でも採用されているが、自社でCommon Lisp処理系を作っていたものの後にLucidのOEM版に切り替えてしまったメーカーも多い。
Lucid CLのコミュニティの目立った資料が残っていないのは、Allegro CLやLispWorksのようにマシンアーキテクチャを跨いでまとまったコミュニティというものが形成され難かったからかもしれない。

MCLのメーリングリストの方は、MCL 3.0のおまけの付属資料をmhonarcにかけたもの。

立ち上げ最初は、Macintosh Allegro Common Lisp という名称だったためか、maclという名称になっている。
ベンダーがApple→Digitoolと変遷している様子も伺える。

おまけに、ml.cddddr.org のトップページを少し整理してみた。

個人的におすすめなレトロメーリングリストは、LISP-FORUM at MIT-MCだ。
MACLISP系で採用されている、#||#コメントが誕生する過程を覗き見ることができるなど色々と興味深いものが多い。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: プログラミング言語Lisp 入門からマルチメディアまで | 9章 マクロ

Posted 2017-02-05 15:55:58 GMT

Lisp本積読解消は、今回もプログラミング言語Lisp 入門からマルチメディアまで
9章 マクロ を読むことにする

9章 マクロ

マクロも特殊形式スペシャルフォームと言うこともある等々、なんとなく言葉の定義がふわふわした印象が多い章。
Lisp族一般の用語として、引数の評価が通常の関数とは異なるという意味での特殊形式スペシャルフォームということでは、そうなのかもしれないが、Common Lispでは、Special Form/Operatorはマクロとは一応分離されているのできっちり分けておいても良いような。

ちなみにCommon Lispでは、特殊形式スペシャルフォームはマクロで実装されていても良いし、マクロは特殊形式スペシャルフォームであっても良い(この場合、内部的に利用していなくてもマクロ展開すれば展開形が出てくる必要がある)

マクロの次にリーダーマクロの説明がある。割合に丁寧な説明で、先のマクロと同じ位の分量がある。

次に、局所ローカルマクロの解説。何故かmacroletでは再帰的な定義ができない、という説明がある。
MCLではそうだったのかなと思い古めのMCL 3.0で再帰的なマクロを定義して実際の動作を確認してみたが、問題なく定義できる。筆者の勘違いだろうか……。

その次に、defsetfの説明がある。所謂、短形式の方のみの説明。

結び

用語の定義がふわふわしていたり、局所ローカルマクロの説明が間違っていたりするので、この章に関しては、あまりお勧めできないかも。
Common Lispマクロ解説の本は充実しているので、マクロに関しては他の本を参考にするのが良さそう。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: プログラミング言語Lisp 入門からマルチメディアまで | 6章 再帰関数

Posted 2017-02-01 17:15:00 GMT

Lisp本積読解消は、今回もプログラミング言語Lisp 入門からマルチメディアまで
6章 再帰関数 を読むことにする

6章 再帰関数

数学の帰納法をプログラムで表現することから始まって、フィボナッチ数の計算やテーラー展開等の話題に進み、再帰的なデータ構造としてのリスト処理の解説がある。
また、再帰関数にtraceを仕掛け、動作を観察し、繰り返しと比べて計算効率がどうなっているかを確認する。

本書はプログラミング自体の入門者でも読めるように書かれているので非常に丁寧に書かれていると思った。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: LISP 原書第3版(I) | 13章 構造体

Posted 2017-01-31 10:28:00 GMT

Lisp本積読解消今回はLISP 原書第3版(I)
13章 構造体 を読むことにする

LISP 原書第3版(I)

まず、この本の紹介をすると、Winston & Horn本として割合に有名な本。
原書は1989年に出版されている。
第1版と第3版が邦訳されていて、第1版の方は、Common LispでなくMacLISPなので注意。
第1版邦訳の文体は直訳的で独特。第1版程ではないが第3版も見出し語などには妙な癖がある。

第3版の方もANSI Common Lisp以前の出版だが、CLtL2相当の内容でオブジェクト指向システムの解説もされている。

13章 構造体

要点を押えつつ簡潔な内容になっていて、この章を読めば構造体についての一通りの知識は得られると思う。

この本にもSETFのFはフィールドのFと説明があったのは知らなかった。
実際にそうなのかは謎なのだが、SETFの大元であるMIT Lispマシン開発者のDavid Moon氏がそういうのだからそうなのかもしれない。
※レビュアーとしてMoon氏の名前もあるので、氏からの指摘があったのかもしれない。

私個人としては、MIT Lispマシンが参考にしたDeutsche氏のA LISP machine with very compact programsに、既にSETFQとして登場していて、フィールドと強い関係がありそうな説明はないので実際どうなのかなあと思っている。
寧ろ関数との関係性で説明されているのでFunctionのFである可能性も0ではなさそう。

閑話休題。この章に付属の例題もそこそこ要点を得ているし全体的に良い内容かなとは思った。
ただ翻訳の問題なのか設問の意味が良く分からない内容になっている気はする


HTML generated by 3bmd in LispWorks 7.0.0

真・LispWorks IDE起動時のツールを指定する

Posted 2017-01-29 03:42:44 GMT

以前、LispWorks IDE起動時のツールを指定する方法として、起動ツールが指定されている隠し変数を変更する、という方法を紹介した。

起動ツールの指定方法位マニュアルに書いていて欲しいものだと思ったが、実は正しい方法が他にあることをLispWorksユーザーのメーリングリストで知った。

その正しい方法だが、LispWorksでは、actionという概念があり、イメージの起動から終了までのイベントにフックを掛けるポイントがあり、ユーザーは任意にactionを追加したり上書きしたりできるらしい。

(lw:print-action-lists)で一覧を取得可能だが、IDEのツールの起動は、"Initialize LispWorks tools"の下の"Create default the tools"で設定されている模様。
標準では、lw-tools:editorと、lw-tools::lispworks-podiumが指定されているのだと思うが、これを上書きし、lw-editorのみとしてみる。

(lw:define-action
 "Initialize LispWorks tools"
 "Create default the tools"
 (lambda (screen)
   (capi:find-interface 'lw-tools:editor
                        :screen screen))
 :after "Configuration restored")

define-actionの長所は、定義したactionは明示的に呼び出さなくても良い所。定義をずらっと並べておくだけで良いのは楽。

落とし穴的な所は、初期化ファイルのように先頭から順に読んで行くわけではないので、ライブラリをロードした結果として使えるようになるシンボルを予め記述しておく訳にはいかない所。簡単にいうと、どうしても記述したいなら、uiop:symbol-callのようなものが必要になる訳である。

むすび

これまで、.lispworksはEmacsの初期化ファイルのように、ずらずら式を並べて書いていたが、Quicklispの初期化などもactionで記述したらすっきり見通しが良くなった。

(lw:define-action
 "When starting image"
 "Quicklisp Settings"
 (lambda ()
   #-quicklisp
   (let ((quicklisp-init "/l/quicklisp/setup.lisp"))
     (when (probe-file quicklisp-init)
       (load quicklisp-init)))
   (asdf/find-system:initialize-source-registry)))

やはり、マニュアルは全部読もう


HTML generated by 3bmd in LispWorks 7.0.0

Common LispのDOLISTを作るのは案外難しい

Posted 2017-01-29 03:02:31 GMT

先日記事に書いたMKCLのDOLISTのマクロ展開の問題だが、紆余曲折の後に改善された。

最初に問題を報告したのは、MKCLだったが、ECLにも同様の問題があるので、全く同じものを報告。
ECLの方も色々あったが、型宣言の問題は解消した。

DOLISTのマクロを組む上で難しい、というかややこしい点に、

  1. ボディ内では、タグが使える(GOできる)
  2. 結果節では、繰り返し変数がNILに束縛される

というのがある。

繰り返し変数に型宣言を付けることが可能だが、最後にNILになるとすると、安直に同じスコープ内に置くと宣言と競合してしまう。
また、ボディ内でGOを使うことは殆ど無いとはいえ、そういう仕様なのである。

(dolist (x '(1 2 3 4))
  (when (oddp x) (go next))
  (print x)
  next)
⊳ 2 
⊳ 4 
→ nil

さらに細かい所では、結果節に型宣言を置いたら機能してしまうようなマクロ展開結果になってもまずい。

歴史的にはDOLISTDOで組まれていて、DOも元々は、PROGで組まれていて、という風にタグが使える構文を下敷にしてきたため、暗黙的にタグが使える。
思えば、繰り返し構文のボディでタグが使えないのはLOOP位だ。

むすび

DOLISTDOTIMESは歴史的にはDOよりは抽象度の高い構文と考えられて来たと思うが、利用する機能としては単純である。
その為、ECLや、MKCLではブートストラップ付近で定義しているが、道具があまり揃っていないブートストラップ時なので結構大変そうだ。
ブートストラップ時はCL:DOLISTを単純にした構文とし、後の潤沢な環境でCL:DOLISTを定義するのが簡単そうだが……。
ちなみに、MKCLでは、MKCL:DOLIST!というのを定義して使っていたりはする模様。


HTML generated by 3bmd in LispWorks 7.0.0

カッコのない国

Posted 2017-01-28 11:50:00 GMT

図書館でコンピュータ系の本を検索し、一覧を眺めていたら、妙なタイトルの本を発見。
どうも子供向けのコンピュータ科学の本らしいが、Land of Lispの住人としては、子供にカッコのない国なぞを教えるとは何事じゃいと思ったので早速借りて読んでみた。

カッコのない国

どうもこの本は、はじめて出会うコンピュータ科学というシリーズの第4巻で、コンパイラがテーマらしい。

後置記法で会話するウシーロ国、前置記法で会話するマーエ国、中置記法で会話するが開始は必ずカッコ終了はコッカを付けるカッココッカ国があり、それぞれの特徴を普通の国である日本語とどう違うのかを考える。

日本語とウシーロ国の翻訳するアルゴリズムなども紹介され、洞穴をスタックに喩えて処理方法を説明する。

後置記法であれば演算子の優先順位も必要なく、カッココッカも要らないことが説明され、ウシーロ国王から、日本語は何故にカッココッカを使ったり使わなかったり複雑なルールであるのかと問い掛けられる。

日本語を含めて四種類の方式が説明されるが、ウシーロ国と日本語の比較が主で、マーエ国とカッココッカ国の影が薄い。

Land of Lispの住人は、マーエ国民かつカッココッカ国民であるわけだが、カッココッカ国を中置としてしまったのはちょっと残念に思った。しかし、まあカッココッカという用語が子供の心に残るなら良いかなとも思った。

内容とは全く関係ないが、カッココッカ国で製造される名物清涼飲料水がコカカーコであることが印象に残った。


HTML generated by 3bmd in LispWorks 7.0.0

バグ報告: LispWorks/システム関数の型宣言が合ってない

Posted 2017-01-23 20:28:36 GMT

パッケージ名を短かく表示する自作ユーティリティをLispWorksに移植してみていて、妙なエラーに遭遇

(proclaim '(optimize (debug 3) (safety 3)))

(defun foo (string package) (system::find-external-symbol String Package))

のようなものをコンパイルして呼び出すと

(foo "PROGN" (find-package :cl))
⊳ Error: The results of SYSTEM::FIND-EXTERNAL-SYMBOL, (PROGN 403), do not satisfy the ftype specifier (VALUES SYMBOL SYMBOL).

とエラーになってしまう。

system::find-external-symbolの型を確認してみると、

(function-information 'system::find-external-symbol)
→ :function 
   nil 
   ((ftype function (simple-string package) (values symbol symbol))) 

となっているが、実際に呼び出してみると

(system::find-external-symbol "PROGN" (find-package :cl))
→ progn 
   403

(system::find-external-symbol "PROGZ" (find-package :cl)) → 0 nil

となっているので返り値の型宣言である(values symbol symbol)とは不整合があるのが問題のようだ。

またコンパイラの設定にも依存していて、safety 3でないとチェックには引っ掛からない。

回避方法

(defun foo (string package)
  (declare (ftype (function (simple-string package)
                            (values (or symbol fixnum) (or symbol fixnum)))
                  system::find-external-symbol))
  (system::find-external-symbol String Package))

こんな感じに宣言をシャドウ?してやれば通る。
また、グローバルにproclaimとかファイルローカルにdeclaimとかしても良いっぽい。

(proclaim '(ftype (function (simple-string package)
                            (values (or symbol fixnum) (or symbol fixnum)))
                  system::find-external-symbol))

報告と回答

どうでも良さそうなバグだけど一応報告してみた。
回答は、内部シンボルは使わないで、ということで終了。

結局、LispWorksの内部的に型宣言は合っているのか、それとも間違っているのかは不明なままだった。

コンパイラのセッティングで無視するなら、(values t t)にでもしちゃえば良いのにね、と思ったり。

むすび

(proclaim '(optimize (debug 3) (safety 3)))しておくと、コンパイラが型宣言の間違いを発見してくれることが多いのでお勧め。
自作のライブラリを公開する時にも、(optimize (debug 3) (safety 3)))で一度通るか確認してから、出荷設定でビルドするのが良いと思う。

ちなみに、パッケージ名を短かく表示する自作ユーティリティとは、こちら

端的に言ってしまうと処理系の改造なのでLispWorksのような商用処理系で試すのはお勧めしない(ソースが公開されている処理系でもお勧めはしないが)
一応LispWorksでも動くようになったので使ってみている。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: プログラミング言語Lisp 入門からマルチメディアまで | 4章 制御構造

Posted 2017-01-21 20:46:32 GMT

Lisp本積読解消は、今回もプログラミング言語Lisp 入門からマルチメディアまで
4章 制御構造 を読むことにする

4章 制御構造

分岐構文各種の紹介から始まって、繰り返し構文のdolistdotimes、単純loopを紹介し、写像関数を解説。
Common Lispの制御構造の初歩はこれを読めばマスターできるのではないかと思った。
まあ、細かいこと言い出すと切りがないのだが、必要な事項が手短に纏まっていて良いのではないだろうか。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: プログラミング言語Lisp 入門からマルチメディアまで | 1章 LISPとは

Posted 2017-01-20 03:05:22 GMT

Lisp本積読解消は、今回もプログラミング言語Lisp 入門からマルチメディアまで
1章 LISPとは を読むことにする

1章 LISPとは

LispといえばAIだが、この本では普通のアプリを記述する言語にフォーカスしていることを説明。
次に、Lispの基本である、コードがリストというデータで表現されていること、多様なデータ型が存在していること、変数について、が、ざっと紹介される。

なお、この本では、S-ExpressionをS表現と呼ぶ。

setfの説明で、「一般化されたメモリ参照」の概念が出てくるなど、若干詰め込み過ぎのきらいはあるが、全体的にLispについての最初の説明としては手頃なのではないかと思った。


HTML generated by 3bmd in LispWorks 7.0.0

祝ManKai Common Lisp 1.1.10 リリース & バグ発見

Posted 2017-01-18 20:35:55 GMT

最近新しいリリースがなかったManKai Common Lisp(MKCL)だが、久々に新リリースが出た。

早速インストールしてみたが、Quicklispのライブラリをいくつか読み込むとエラーになってしまった。

原因を探ってみたが、どうもdolistのマクロ展開で型宣言が間違った形になってしまうのが原因のようだ。

(dolist (string string-list)
  #-:genera (declare (string string))
  (incf total-size (length string)))

のようなものが、

(block nil
  (let* ((si::%dolist-var string-list) string)
    (declare (string string))
    (si:while si::%dolist-var
      (setq string (first si::%dolist-var))
      (incf total-size (length string))
      (setq si::%dolist-var (rest si::%dolist-var)))
    nil))

と展開されてしまう為、letstringで宣言した変数に初期値としてnilが入ってしまう。

修正

問題の箇所は、/src/lsp/export.lsp内の定義

;;
;; This is also needed for booting MKCL. In particular it is required in
;; defmacro.lsp.
;;
(let ((f #'(si::lambda-block dolist (whole env)
       (declare (ignore env))
       (let (body pop finished control var expr exit)
         (setq body (rest whole))
         (when (endp body)
           (simple-program-error "Syntax error in ~A:~%~A" 'DOLIST whole))
         (setq control (first body) body (rest body))
         (when (endp control)
           (simple-program-error "Syntax error in ~A:~%~A" 'DOLIST whole))
         (setq var (first control) control (rest control))
         (if (<= 1 (length control) 2)
         (setq expr (first control) exit (rest control))
         (simple-program-error "Syntax error in ~A:~%~A" 'DOLIST whole))
         (multiple-value-bind (declarations body)
         (process-declarations body nil)
           `(block nil
         (let* ((%dolist-var ,expr)
            ,var)
           (declare ,@declarations)
           (si::while %dolist-var
              (setq ,var (first %dolist-var))
              ,@body
              (setq %dolist-var (rest %dolist-var)))
           ,(when exit `(setq ,var nil))
           ,@exit)))))))
  (si::fset 'dolist f t))

si:whileの箇所を

`(block nil
   (let* ((%dolist-var ,expr)
          (,var (first %dolist-var)))
     (declare ,@declarations)
     (si::while %dolist-var
        ,@body
        (setq %dolist-var (rest %dolist-var))
        (setq ,var (first %dolist-var)))
     ,(when exit 
        `(let ((,var nil))
        (declare (ignorable ,var))
           ,@exit))))

のように修正すれば良いだろう。

これで展開がこうなる

(block nil
  (let* ((si::%dolist-var string-list) (string (first si::%dolist-var)))
    (declare (string string))
    (si:while si::%dolist-var
              (incf total-size (length string))
              (setq si::%dolist-var (rest si::%dolist-var))
              (setq string (first si::%dolist-var)))
    nil))

これはどうもECLから引き継いだバグのようで、ECLにも同様の問題がある。

さらに源流を追い掛けてみると、GCLや、KCLにはこの問題はないので、ECLでエンバグしてしまったのではないだろうか。

それにしても、どうしていままで自分は気付かなかったのだろう。

他にも、consで宣言した変数をconspで確認している矛盾/バグもあったりした。 conspnilになるのはnilが来た時であるので、nilを期待しているとすれば、型は、listでなければならないのだった。

バグ報告には、common-lisp.netのgitlabアカウントが必要なので一応申請してみたが、ちょっと面倒……。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: プログラミング言語Lisp 入門からマルチメディアまで | 3章 関数定義

Posted 2017-01-17 19:01:29 GMT

Lisp本積読解消は、今回もプログラミング言語Lisp 入門からマルチメディアまで
3章 関数定義 を読むことにする

3章 関数定義

defunでの関数定義から始まって、関数定義に関連する、無名関数、ラムダリストを解説し、さらにそれらに関連する関数/変数の束縛構文を解説する。
型宣言や、assertについても手短に解説されていて、この章を読めば一通りのことは把握できる。

気になった所

実装によって、*save-doc-strings*をtにすればドキュメント文字列を保存できると解説されているが、これは実装によってというよりMCL依存の機能。
Clozure CLでもこの伝統は引き継いでいるようで、この変数をnilにするとドキュメント文字列は保存されないようだ。
なお、*save-doc-strings*は正確には、ccl:*save-doc-strings*である。


HTML generated by 3bmd in LispWorks 7.0.0

Macro Forms as Places

Posted 2017-01-17 13:50:39 GMT

(setf f)で標準で定義されている関数について確認していて、関数だけでなくマクロの(setf f)についても定義されているのに今更気付いた。

マクロ展開されてから、setfの展開が適用されると書いてあるが、マクロ定義が適切ならば、何もしなくてもsetfの定義もできているということらしい。

(macrolet ((nthcdr* (n list)
             `(cdr (etypecase ,n
                     ((eql 0) (cons nil ,list))
                     ((eql 1) ,list)
                     ((integer 2 *) (nthcdr (1- ,n) ,list))))))
  (let ((u (list 0 1 2 3)))
    (setf (nthcdr* 2 u) (list :x :y :z))
    u))(0 1 :x :y :z) 

なるほど確かにそうなっている。
ちなみに、試してみれば判ると思うが、setfのフォームでも適切に機能するように組むのはPlaceの要件を満さなくてはいけないので案外難しいと思う。

上記ではローカル定義だが、勿論大域でも問題ない

(defmacro nthcdr* (n list)
  `(cdr (etypecase ,n
          ((eql 0) (cons nil ,list))
          ((eql 1) ,list)
          ((integer 2 *) (nthcdr (1- ,n) ,list)))))

(macroexpand '(setf (nthcdr* 2 u) (list :x :y :z))) ==> (system::%rplacd (etypecase 2 ((eql 0) (cons nil u)) ((eql 1) u) ((integer 2 *) (nthcdr (1- 2) u))) (list :x :y :z)) t

エイリアスにマクロを使うのはコンパイラがインライン展開をしてくれなかった時代の悲しい習慣だと考えていたが一つメリットを発見してしまった。

とはいえ、コンパイラが自動でやってくれるものをあえてマクロを使うメリットはないだろう。
インライン展開をしてくれない処理系の為にそうする、という意見もあるが、インライン展開をしてくれない処理系は即ち、速度を出そうとしていない処理系なので、そこで頑張っても仕方ないのである。

一応速度比較をしてみるが、マクロでもインライン展開でも同じ速度になった。
(disassembleして出てくるコードもほぼ同じ)

(defun function-call-forms-as-place/flet ()
  (declare (optimize (speed 3) (debug 0) (safety 0)))
  (flet ((nthcdr* (n list)
           (nthcdr n list))
         ((setf nthcdr*) (val n list)
           (setf (cdr (etypecase n
                        ((eql 0) (cons nil list))
                        ((eql 1) list)
                        ((integer 2 *) (nthcdr (1- n) list))))
                 val)))
    (declare (inline nthcdr* (setf nthcdr*)))
    (let ((u (list 0 1)))
      (setf (nthcdr* 2 u) (list :x))
      (list u (nthcdr* 2 u)))))

(defun macro-forms-as-place () (declare (optimize (speed 3) (debug 0) (safety 0))) (macrolet ((nthcdr* (n list) `(cdr (etypecase ,n ((eql 0) (cons nil ,list)) ((eql 1) ,list) ((integer 2 *) (nthcdr (1- ,n) ,list)))))) (let ((u (list 0 1))) (setf (nthcdr* 2 u) (list :x)) (list u (nthcdr* 2 u)))))

(progn (time (dotimes (i 1000000))) (time (dotimes (i 1000000) (macro-forms-as-place))) (time (dotimes (i 1000000) (function-call-forms-as-place/flet))))

;;; LispWorks 7.0 x86_64 Linux ⊳ Timing the evaluation of (dotimes (i 1000000)) ⊳ ⊳ User time = 1.130 ⊳ System time = 0.000 ⊳ Elapsed time = 1.123 ⊳ Allocation = 1128023752 bytes ⊳ 0 Page faults ⊳ Calls to %EVAL 15000036 ⊳ Timing the evaluation of (dotimes (i 1000000) (macro-forms-as-place)) ⊳ ⊳ User time = 0.970 ⊳ System time = 0.000 ⊳ Elapsed time = 0.965 ⊳ Allocation = 1208018216 bytes ⊳ 0 Page faults ⊳ Calls to %EVAL 16000036 ⊳ Timing the evaluation of (dotimes (i 1000000)(function-call-forms-as-place/flet)) ⊳ ⊳ User time = 0.960 ⊳ System time = 0.000 ⊳ Elapsed time = 0.956 ⊳ Allocation = 1208025808 bytes ⊳ 0 Page faults ⊳ Calls to %EVAL 16000036

→ nil


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: プログラミング言語Lisp 入門からマルチメディアまで | 2章 リスト処理

Posted 2017-01-17 00:30:59 GMT

Lisp本積読解消は、今回もプログラミング言語Lisp 入門からマルチメディアまで
2章 リスト処理 を読むことにする

2章 リスト処理

リストとはLispにおいてどういうものか、から始まって、コンスセルの構造、リスト関数、さらに、リスト(sequenceのサブタイプ)でも使えるシークエンス関数を解説し、セットとしてのリスト、スタックとしてのリストとその処理関数を紹介。また、リストの破壊的操作についても解説。

手頃に過不足なくまとまっていて良さそうと思った。

気になった所

定数の破壊

あるあるではあるが、破壊的関数の紹介でクォートされたリストを破壊する例が多い。
結局の所、Common LispでREPL内で定数リストの破壊は許容されるのだろうか。
ファイルに記載されたコードだと、ファイル内で字面が同じであれば同じオブジェクトとして解釈されることがあるので厄介なバグになることがあってまずい。しかし、REPLであれば、毎度新規に作られるのだろうからOKなのだろうか(まあ、そこまで考えてないと思うけど)

(setf nthcdr)

(setf nthcdr)を用いて解説する例が多いが、(setf nthcdr)は、標準で装備されているとは限らない。

Several kinds of places are defined by Common Lisp; this section enumerates them. This set can be extended by implementations and by programmer code.

とのことなので処理系が拡張しても良いのではあるが、これを使用すれば可搬性は低いコードになる。
一応入門書なので可搬性は高い方が嬉しかったかも。


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: プログラミング言語Lisp 入門からマルチメディアまで | 10章 他のデータ型

Posted 2017-01-15 19:25:39 GMT

Lisp本積読解消は、今回もプログラミング言語Lisp 入門からマルチメディアまで
10章 他のデータ型 を読むことにする

10章 他のデータ型

他のデータ型とはリスト意外のデータ型のこと、配列、構造体、ハッシュテーブル、属性リスト、連想リスト、クラス辺りが紹介される。
その後、型変換としてcoerceの使い方が紹介され、その他にも、文字列→シンボル、文字→数字等の変換が紹介される

気になった所

(concatenate 'array #(0 1 2 3) #(4 5 6 7))

という例が出てきたが、これはANSI CLでは不正。
concatenateの型指定子はsequenceでなければならない。
MCLでは通り、GCLでも通るのでCLtL1時代の、arraysequenceの使い分けが若干曖昧だった頃は合法だったのかもしれない。

また、

(coerce 65 'character) ;=> #\A

というのも出てくるがこれもANSI CLでは不正。
どうもint-charの廃止に伴なってこの変換もなくなったらしい。 これもMCLなら通る。

ほか、

(mapcar #'(lambda (x) (read-from-string (string x)))
        (coerce "123" 'list))
;=> (1 2 3) 

のようなものがちらほら出てくるが、上記であれば、

(map 'list #'digit-char-p "123")

のように、より適切により簡潔に書ける例が多い。

この章の解説は、大筋では良いと思うけれど、どうも詰めが甘いのと、CLtL1の仕様を紹介している所が残念に感じた。

この本は割合に網羅的な解説となっていてリファレンス的にも使えるが、この章に関しては、間違った例が多いのでリファレンスとしては使えないかも。


HTML generated by 3bmd in LispWorks 7.0.0

Older entries (2066 remaining)