#:g1: frontpage

 

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

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が置き換えたフォームを更に展開し展開が止まらなくなるため。)
上記では、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

DESCRIBEでオブジェクトのスロット名をパッケージ名付きにする

Posted 2017-01-14 22:45:26 GMT

Allegro CL、Clozure CLでは、standard-objectdescribeした場合、スロット名は、パッケージ名付きで表示される。
LispWorksや、SBCL、CMUCLだと、見易さ重視なのかシンボル名だけになっているが、これだと適当にオブジェクトをslot-valueで中身を弄って検分する時にシンボルの特定が非常に面倒臭い。

これをどうにかしようということで、とりあえずソースが弄れるSBCLの中身がどうなっているか追い掛けてみたところ、sb-impl::describe-instanceが表示しているらしい。

スロットは、ローカル関数のdescribe-slotで表示していて、これがシンボルを~Aで表示しているので、~Sに変更し期待通りの表示となった。

#|||
sb-impl::describe-instance
  [symbol]

describe-instance names a compiled function: Lambda-list: (object stream) Derived type: (function (t t) (values null &optional)) Source file: SYS:SRC;CODE;DESCRIBE.LISP.NEWEST |||#

(defun describe-instance (object stream) (let* ((class (class-of object)) (slotds (sb-mop:class-slots class)) (max-slot-name-length 0) (plist nil))

;; Figure out a good width for the slot-name column. (flet ((adjust-slot-name-length (name) (setf max-slot-name-length (max max-slot-name-length (length (symbol-name name)))))) (dolist (slotd slotds) (adjust-slot-name-length (sb-mop:slot-definition-name slotd)) (push slotd (getf plist (sb-mop:slot-definition-allocation slotd)))) (setf max-slot-name-length (min (+ max-slot-name-length 3) 30)))

;; Now that we know the width, we can print. (flet (#|(describe-slot (name value) (format stream "~% ~A~VT = ~A" name max-slot-name-length (prin1-to-line value)))|# (describe-slot (name value) (format stream "~% ~S~VT = ~A" name max-slot-name-length (prin1-to-line value)))) (sb-pcl::doplist (allocation slots) plist (format stream "~%Slots with ~S allocation:" allocation) (dolist (slotd (nreverse slots)) (describe-slot (sb-mop:slot-definition-name slotd) (sb-pcl::slot-value-for-printing object (sb-mop:slot-definition-name slotd)))))) (unless slotds (format stream "~@:_No slots.")) (terpri stream)))

LispWorksは、ソースがないので変更は無理かなと思ったが、sys::*describe-attribute-formatter*という変数が存在し、これが、~Aになっていたので、試しに~Sにしてみたところパッケージ名が表示された。

CL-USER 35 > (defclass zot () (sys::x sys::y sys::z))
#<standard-class zot 402017A113>

CL-USER 36 > (describe (make-instance 'zot))

#<zot 4020187ABB> is a zot x #<unbound slot> y #<unbound slot> z #<unbound slot>

CL-USER 37 > (setq sys::*describe-attribute-formatter* "~S") "~S"

CL-USER 38 > (describe (make-instance 'zot))

#<zot 402018EC0B> is a zot system::x #<unbound slot> system::y #<unbound slot> system::z #<unbound slot>

CL-USER 39 >

この他、sys::*default-describe-object-label*で体裁を変更できるらしい。初期値は、"~%~A ~VT "

割合にLispWorksにはユーザーがカスタマイズできる変数が用意されていることが多い気はしている(がドキュメントはあったり無かったり)


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: プログラミング言語Lisp 入門からマルチメディアまで | 5章 入出力

Posted 2017-01-14 18:17:59 GMT

Lisp本積読解消は、今回もプログラミング言語Lisp 入門からマルチメディアまで
5章 入出力 を読むことにする

5章 入出力

入出力について割合に網羅的に説明してある。
シンボルの読み取りから、ファイル入出力、パス名、formatの簡単な説明で、この章を読めば一通りのことを知ることができるだろう。

気になった所

MCLでは、ストリームがオブジェクトシステムと統合されていて、standard-classのサブクラスになっている。
この章では、これが前提になっていて、slot-valueで、ストリームのスロットを覗いてみたりしているが、残念ながら現状の処理系では統合されていない方が多い(ANSI Common Lispでは、built-in-classのサブクラスと規定されている)。

ちなみに、SBCLでは構造体にslot-valueが使えるので同様のことが可能。
できていることは同じだが、実現方法がちょっと違う。

(slot-value (make-string-output-stream) 'sb-impl::index)

ユーザーが定義可能なストリームは、Gray StreamsからSimple Streamsと提案されているが、規格には入らなかったものとして惜しまれる機能の代表格だ。

Common Lispの先祖のLisp Machine LispではFlavorsと統合されていたが、オブジェクトシステムをCLtL1で外してしまったために、ある意味退歩する結果となってしまった。

先行するシステムの機能がCLtL1では削られる結果になってしまった事例には他に、繰り返し構文のloopの本来の仕様、エラーハンドリングシステム(コンディションシステム)があるが、こちらはANSI Common Lispで戻ってきた。

参考


HTML generated by 3bmd in LispWorks 7.0.0

Lisp本積読解消: プログラミング言語Lisp 入門からマルチメディアまで | 8章 シンボルとパッケージ

Posted 2017-01-13 19:28:43 GMT

良さそうなLisp本を見付けては購入を繰り返していて、いつの間にかLisp本が大量に積まれる状況となってしまった。

ブログのネタにでもしたら、積読解消になるかなと思い、読んだ感想を適当にブログに書いてみることにする。

今回は、プログラミング言語Lisp 入門からマルチメディアまでの8章を読む。

この本について

プログラミング言語Lisp 入門からマルチメディアまで
プログラミング言語Lisp 入門からマルチメディアまで

翻訳書でなく、ANSI Common Lispをターゲットに書かれた本として貴重かもしれない。
ページ数は750ページと結構厚い。
割と網羅的に書かれているが、ウェブサーバーの紹介が、cl-httpだったり、GUI周りの説明が、この本のターゲットであるMCLに依存したものとなっていたりする。
しかし、Common LispではGUIも規格の外であるので、やむを得ないのだった。

8章 シンボルとパッケージ

シンボルとパッケージについて一通り丁寧に解説してある。
importexportshadowあたりの関数を説明し、最後にまとめて、defpackageの説明があり、この章を読めば、Common Lispのパッケージとシンボルについては理解できるだろう。

気になった所

シンボルの導入の順番について記述があり、ファイルには、

  1. defconstant
  2. defparameter
  3. defvar
  4. マクロと関数

の順で記載しなければならない、とある。

defconstantと マクロと関数 の順番は分からなくもないが、defparameterdefvar の順番、defconstantの後に、defparameterが来ないといけない理由はなんだろうか。
ANSI Common Lisp的には特に指定はないと思うが……。

私なりに理由を考えてみたが、

  • defconstantはコンパイル時に評価されるので、依存関係としては、とりあえず先頭に記述するのが妥当
  • defvarは宣言より先に値があるかないかで挙動が変わるので、その特長を活かすことを考えると都度値が変化する可能性のあるdefparameterの後ろになる(変数名が被った場合)

;;; A
(defvar *foo* 42)
(defparameter *foo* (* *foo* 2))

;;; B
(defparameter *foo* 42)
(defvar *foo* (* *foo* 2))

上記AとBであれば、Bの方が動作としては望ましいようなそうでもないような。
そもそも名前は被せないと思うので、ちょっと苦しい。

これら順番について何か妥当な理由はあるだろうか。


HTML generated by 3bmd in LispWorks 7.0.0

五反田記号処理ランチというのを開催してみたい

Posted 2017-01-11 17:13:50 GMT

五反田記号処理ランチというのを開催してみたいのだけれど、どうだろうか。

内容

昼時に会議室を借りておき、五反田近辺の人が持参した弁当を食べて帰る会。 同時に記号処理系の発表をする。

  • Lisp
  • Prolog
  • その他、記号処理系の何かであればなんでも良い(Maxima、Reduce、Mathmatica、etc)

とりあえず、Lispってどうなんですか?みたいな漠然とした話でも良いです。

場所と時間

五反田の格安の会議室

12:30/14:30 の2時間

費用は、2800円で、18名まで収容可能。飲食は可能

入れ替わり可として都合14名が参加すれば一人200円で収まる。

職場が五反田の人が弁当持参でふらっと来て30分位で帰るようなものを想定。

とりあえず4名(700円)位参加してみたい、という声があれば場所を予約して開催してみたいと考えている。

開催までの流れ

  1. 初回4名位集まりそうになる
  2. 会議室を予約する
  3. 当日各人が昼食を食べに集まる
  4. 解散

開催は月曜か木曜だと嬉しい。

興味のある方は、このブログかRedditにでもコメント下さい。
4名位集まらないかなあ。


HTML generated by 3bmd in LispWorks 7.0.0

Conniver風のdefun

Posted 2017-01-08 18:39:34 GMT

Conniverではcdefunのボディ中で"aux"が使えたが、これを真似して更に拡張したものを考えてみた。

(deftype bind-form ()
  '(member &let &let* &multiple-value-bind &flet &labels
           &macrolet &symbol-macrolet))

(defun expand-&form (body) (typecase body (atom body) (cons (typecase (car body) (bind-form `((,(intern (subseq (string (car body)) 1)) ,@(expand-&form (cdr body))))) (T `(,(expand-&form (car body)) ,@(expand-&form (cdr body))))))))

(defmacro defun& (name (&rest args) &body body) `(defun ,name (,@args) ,@(expand-&form body)))

このdefun&を使うと、この式が

(defun canonicalize-amazon-url (url &optional (affiliatep t))
  (let ((uri (puri:parse-uri url)))
    (declare (type puri:uri uri))
    (flet ((canonicalize-path (path)
             (cl-ppcre:regex-replace ".*/dp/([^/]+)/.*" path "/dp/\\1/"))
           (canonicalize-path/affiliate (path)
             (cl-ppcre:regex-replace ".*/dp/([^/]+)/.*"
                                     path
                                     "/exec/obidos/ASIN/\\1/lisphub-22"))
           (uri-to-string (uri)
             (with-output-to-string (out) (puri:render-uri uri out))))
      (multiple-value-bind (path win)
                           (funcall (if affiliatep
                                        #'canonicalize-path/affiliate
                                        #'canonicalize-path)
                                    (puri:uri-path uri))
        (and win
             (uri-to-string
              (make-instance 'puri:uri
                             :scheme (puri:uri-scheme uri)
                             :host (puri:uri-host uri)
                             :path path)))))))

こんな感じに書ける。

(defun& canonicalize-amazon-url (url &optional (affiliatep T))
  &let ((uri (puri:parse-uri url)))
    (declare (type puri:uri uri))
  &flet ((canonicalize-path (path)
           (ppcre:regex-replace ".*/dp/([^/]+)/.*"
                                path
                                "/dp/\\1/"))
         (canonicalize-path/affiliate (path)
           (ppcre:regex-replace ".*/dp/([^/]+)/.*"
                                path
                                "/exec/obidos/ASIN/\\1/lisphub-22"))
         (uri-to-string (uri)
           (with-output-to-string (out)
             (puri:render-uri uri out))))
  &multiple-value-bind (path win) 
                       (funcall (if affiliatep 
                                    #'canonicalize-path/affiliate
                                    #'canonicalize-path)
                                (puri:uri-path uri))
  (and win
       (uri-to-string
        (make-instance 'puri:uri 
                       :scheme (puri:uri-scheme uri)
                       :host (puri:uri-host uri)
                       :path path))))

インデントが浅くできることを狙ったのだが、うーん、いまいち……。

&の代りに<を使うと割合に違和感がない。

(<defun canonicalize-amazon-url (url &optional (affiliatep T))
  <let ((uri (puri:parse-uri url)))
    (declare (type puri:uri uri))
  <flet ((canonicalize-path (path)
           (ppcre:regex-replace ".*/dp/([^/]+)/.*"
                                path
                                "/dp/\\1/"))
         (canonicalize-path/affiliate (path)
           (ppcre:regex-replace ".*/dp/([^/]+)/.*"
                                path
                                "/exec/obidos/ASIN/\\1/lisphub-22"))
         (uri-to-string (uri)
           (with-output-to-string (out)
             (puri:render-uri uri out))))
  <multiple-value-bind (path win) 
                       (funcall (if affiliatep 
                                    #'canonicalize-path/affiliate
                                    #'canonicalize-path)
                                (puri:uri-path uri))
  (and win
       (uri-to-string
        (make-instance 'puri:uri 
                       :scheme (puri:uri-scheme uri)
                       :host (puri:uri-host uri)
                       :path path))))

一つ問題があって、束縛構文のスコープがCDR全体に及ぶような場合しか適切に扱えない。

(defun foo (n)
  (let ((n n)) n)
  (let ((n n)) n))

のようなものは、こう書くことになる。

(defun& foo (n)
  (let ((n n)) n)
  &let ((n n)) n)

むすび

試してみたけれど、ぱっとしなかった。

ちなみに、エディタでインデントを浅くしても同じ効果は得られる


HTML generated by 3bmd in LispWorks 7.0.0

Emacs LispのEIEIOはCLOSではない

Posted 2017-01-05 20:53:49 GMT

個人的にCommon Lispのオブジェクト指向システムをCLOSとは呼ばないようにする試みを実施中だが、ここではCLOSと書くことにする

EIEIOは、CLOSではない。

まず、EIEIOは、CLOSではない。
Common Lisp風のAPIを持つEmacs Lispのオブジェクト指向拡張である。
具体的には、Emacs Lisp上で総称関数を再現しているが、主な違いは、

  • ANSI Common Lispでは言語仕様の中で統合されている(ライブラリではない)
  • CLOSでは設計思想の背景としてMOPのサポートが前提である
  • EIEIOはCommon Lispの仕様を忠実に再現しようとしたものではない(またそれが目的でもない)

位だろうか。

EIEIOをCLOSと考えた場合の弊害

少しEIEIOを試したり、EIEIO近辺を眺めた感想程度だが、EIEIOをCLOSと考えた場合の弊害には、

  • なまじAPIがCLOS風なのでCLOSと思って書くと色々と落とし穴にはまる
  • CLOSの解説書の内容をEIEIOに適用しても色々と動かない(主要機能でさえ)
  • EIEIOはCLOSのサブセットでもなく独自拡張があるが、この拡張はMOPを意識したものでもないため断絶がある
  • EIEIOをCLOSと考えて色々と上手く行かなかった結果CLOSが駄目という話をしてしまう

位があるだろうか。

MOPを意識するかどうかというのは、CLOSではMOPが前提なので拡張するとなればMOPに従うことになることを指している。EIEIOには特に従うべき規約はない。
結果として、EIEIOの拡張をCLOSに取り込もうとした場合、不自然だったりする。

設計思想の相違

Common Lispに限らずLisp上には様々なオブジェクト指向システムが構築されてきたが、CLOSの場合は「プログラミング可能なプログラミング言語であるLisp」のオブジェクト指向システムはどうあるべきであるかを考えた結果であり、オブジェクト指向システムをLispに取り込んだというよりは、オブジェクト指向システムをLisp化したという面が大きい。

この背景を知らないと意味が分からない機能が多いし、実際あまり理解されていないので無用な機能が豊富とされることも多い。
「プログラミング可能なオブジェクト指向システム」のためにMOPがサポートされるが、こちらが理解され難いことについても同様である。

EIEIOはCLOSのAPIを真似ただけで、「プログラミング可能なオブジェクト指向システム」を実現したいわけではないと思う。
CEDITという統合環境を実現する計画の中で、OOPの枠組みとしてCLOSを参考にしたのが発端であり、当然ながらCEDITありき、である。

むすび

EIEIOは、CLOS風の総称関数風インターフェイスを持つOOP拡張位に考え、見た目は似ているがまったくの別物と考えた方がEmacs Lispコミュニティ、Common Lispコミュニティ双方にとって幸せであると思う。

EIEIOは、Common Lispの資産(設計思想/教材)を活かすような実装でもないし、「プログラミング可能なオブジェクト指向システム」を目指しているわけでもないし、ベースになる言語に統一されているわけでもないし、EIEIOをCLOSと考えて良いことは何もない。

同じMACLISP方言だけに、Common Lispの機能をEmacs Lispに導入しようという試みは多いが、別にCommon Lispに倣う必要など全くない。実際資産の行き来も殆どないのだし。

MACLISP方言だけでも、多重継承、多重メソッドなしのFlavors、多重継承、多重メソッドなしで隠蔽機能に取り組んだCommonObjects、プロトタイプ指向のObject LISP、プログラミング可能なオブジェクト指向システムであるCLOSと多種多様であった。
Emacs Lispは、Emacs Lispらしいオブジェクト指向システムを構築しても良いのではないだろうか。
(蛇足だが、Emacsのメーリングリストを眺めていてもRMSの発言の背景には、こういうLispの試行錯誤の歴史の中に居た人という所があるが、他の人達にはこういう視点はないので、話が噛み合っていないことが多々あるように思える)

コンパクトなCLOS系のオブジェクト指向システムとしては、ISLISPのILOSがある。
同じ追い掛けるなら、設計段階から考えて機能をコンパクトに整理(MOPもなし)しているという点では、ILOS方が良いのではないだろうか(まあ、無いと思うが)


HTML generated by 3bmd in LispWorks 7.0.0

TOPS-20 Common Lisp ビルドメモ

Posted 2017-01-03 17:16:45 GMT

久々にTOPS-20 Common Lispをビルドしてみようと思い立ち、あれこれ試してみていたが、これまで手順をメモっておかなかったため、何をどうすれば良いのかさっぱり分からなくなっていたので、備忘録としてここに記録しておく。
自分以外にもTOP-20 Common Lispに興味がある誰かの役に立てば……(まずそんな人はいない)。

ビルドメモとはいえ、残念ながらこれまでビルドに成功してはおらず、処理系のコアが起動するのみである。

以下、手順

TOPS-20を用意する

どうにかして用意する。
エミュレータがあるので、これを利用するのが手っ取り早いかも。

TOPS-20 Common Lisp の処理系のファイルを用意

www.softwarepreservation.orgにテープイメージのリンクがあるので、これに従ってファイルを抽出し、TOPS-20の任意のディレクトリに展開する。

ラトガース大学のTOPS-20 Common Lispは、1982年のLFP(Lisp and Functional Programming)でのCommon Lispのお披露目の際に、現在製作中の処理系として紹介されており、歴史的には何となく重要そうな処理系である。
もしかしたら、ワードマシン上のCommon Lisp処理系としては貴重かもしれない。

展開されたソースを眺めると、CMUのSpiceプロジェクトが配布していたCommon Lisp処理系作成キットをベースにしていることが分かる。
素のCMUのCommon Lisp処理系キットがどこかに転がってないかなあ。

ディレクトリにファイルを展開する

適当に展開しよう

MIDASファイルをアセンブルする

boot.midファイルがあるのでアセンブルする

[Toad-1] LS:<~>@ dir boot*

LS:<MASSO> BOOT.INIT.1 .MID.1 .REL.3

Total of 414 pages in 3 files

アセンブル

[Toad-1] LS:<~>@ midas boot.mid

KERNEL for Common Lisp RNAMEF+16 7566 1. 23-949 %MRGNA Undefined in IFN RNAMF1+15 7647 1. 23-999 %MRGNA Undefined in IFN DIRECT+3 7751 1. 23-1075 %MRGNA Undefined in IFN ARFCAL 11475 1. 29-253 %SPREF Undefined in IFN AREF1+16 11522 1. 29-270 %SPREF Undefined in IFN AREESC+1 12044 1. 29-477 %SPREF Undefined in IFN ASTCAL 12065 1. 29-488 %SPSET Undefined in IFN ASET1+16 12112 1. 29-505 %SPSET Undefined in IFN ASEESC+1 12362 1. 29-670 %SPSET Undefined in IFN IPRIN+15 14555 1. 33-041 %OUTPR Undefined in IFN PRCHR+24 14656 1. 33-122 %CHRNA Undefined in IFN PRCHR2+3 14705 1. 33-144 %CHRNA Undefined in IFN PRSRUC+7 15532 1. 33-653 %OUTST Undefined in IFN PRARH1+20 15574 1. 33-685 %OUTVE Undefined in IFN PRYAR 15604 1. 33-690 %OUTAR Undefined in IFN PRIVC+15 15631 1. 33-707 %OUTVE Undefined in IFN EVALUB+21 23731 1. 34-351 %SIGCE Undefined in IFN CUDF+15 24042 1. 34-407 %SIGCE Undefined in IFN EVMACR+2 24204 1. 34-544 .MEXPH Undefined in IFN MEXLCY+10 25334 1. 34-1303 .MEXPH Undefined in IFN CALUNE+11 32161 1. 40-051 %SIGCE Undefined in IFN D.FER0 56775 1. 48-006 %ERROR Undefined in IFN D.CER0 57003 1. 48-009 %CERRO Undefined in IFN D.FER1 57011 1. 48-012 %ERROR Undefined in IFN D.CER1 57017 1. 48-015 %CERRO Undefined in IFN KERNEL for Common Lisp Constants area inclusive From To 404 501 42144 54075 57141 60003 72621 72624 Run time = 1:11.86 8272 Symbols including initial ones (51% used)

リンクする

[Toad-1] LS:<~>@ link
boot
sys:forlib/seg:low/s
/g

起動してみる(あまり良く分かっていない)

start

?Undefined operation code at #o1033416
Instruction = #o123100042204
"Error signaled by operating system" 

NIL *

変なエラーがでるが、まあ動いているらしい。

cboot.ctlを眺めてみると、この後、cboot.clispを読んだりすれば、フルセットのCommon Lisp処理系にブートストラップできるようだが、ここでこける。

以上、ここで行き止まり。

一応コアしか機能していない状態でもtak位は定義できる。

(defun tak (x y z)
  (if (<= x y)
      z
      (tak (tak (1- x) y z)
           (tak (1- y) z x)
           (tak (1- z) x y))))

*(tak 18. 12. 6.)

7

Living_ComputersのTOAD-1で実行すると(tak 18 12 6)は12秒位で終了。

コンパイルできればもうちょっとは速くなるのかも。

#'takや、(symbol-function 'tak)の結果が

*#'tak

(LAMBDA (X Y Z) (BLOCK TAK (IF (<= X Y) Z (TAK (TAK (1- X) Y Z) (TAK (1- Y) Z X) (TAK (1- Z) X Y)))))

という結果になるのがちょっと面白いかも。

今後の予定

これまでエミュレータではなくて実機(PDP-10互換機)で試しているのでエミュレータを用意して試してみたい。


HTML generated by 3bmd in LispWorks 7.0.0

LispWorks 7.0でlibssl 1.1の読み込みに失敗する

Posted 2017-01-01 12:59:25 GMT

年末年始なのでパソコン関係の整理をしていて、OSのパッケージのアップデートなどもしていたが、ふと気づくとLispWorks 7.0でlibsslの読み込みに失敗するようになってしまっていた。

あれこれ探ってみるとどうやら昨年の8月に出たばかりのOpenSSL 1.1のライブラリを上手く扱えていないらしい。

LispWorks 7.0は2015年の5月にリリースされたものなので、まあ対応していないのはしょうがないのかもしれない。

対処方法を調べてみたが、明示的に読み込むライブラリを指定すれば良いらしい。
マニュアルによると読み込むパスの指定には、comm:set-ssl-library-pathを使う。

ということで下記のようなものを~/.lispworksとか、siteinit.lispに書けば対処できる。

(require "comm")
(comm:set-ssl-library-path "/usr/lib/x86_64-linux-gnu/libssl.so.1.0.2") ; 1.0.2は1.0系の最新

一応LispWorksにもバグ報告を上げてみたけれど、環境依存の問題ということで対処なしでクローズになるような気がしている。
libssl.soのパッケージ標準バージョンは今後1.1になって行くのだろうから対応しておいて欲しいところだが……。

追記: 回答

SSL 1.1への対応は、次期リリースのLispWorksでする予定とのことで、現状では、comm:set-ssl-library-pathで明示するのが最善の対処とのこと。

ということで、private-patches/comm.lispというものを書いて、プライベートパッチとして読み込むことにしてみた。

(lw:defadvice (require comm :around) (module-name &optional pathname)
  (cond ((string-equal module-name "comm")
         (lw:call-next-advice module-name pathname)
         (when (string= "-lssl" comm::*ssl-library-path*)
           (comm:set-ssl-library-path
            "/usr/lib/x86_64-linux-gnu/libssl.so.1.0.2")))
        (T (lw:call-next-advice module-name pathname))))


HTML generated by 3bmd in LispWorks 7.0.0

2016年振り返り

Posted 2016-12-30 19:59:57 GMT

毎年振り返りのまとめを書いているので、今年も書いてみます。

Lisp的進捗

ブログ

2016終了をもってLispのブログを書くのも丸十年になりました。 今年はあまり記事を書いた印象はありませんでしたが、アドベントカレンダーの追い込みで気付けばアドベントカレンダーだけで34記事書いていた模様です。
この記事を入れて都合55記事なので、なんだかんだで週一ペースで書いたことにはなりました。

学習

位です。
レトロLisp処理系については、今迄漠然とした理解が、かなり系統立ったものとなりました。まあ、かなり役に立たない知識ですが……。

LispWorks

LispWorksも購入してから早一年ですが、特に進歩もありません。
一応、通勤時間で、

辺りは通読しましたが、重要そうなLispWorks User Guide and Reference Manualとか、CAPI User Guide and Reference Manualは途中です。
しかし、購入したばかりの電子リーダーを踏んで壊してしまったのでもう通勤時間には読まないかも……。

LispWorksのマニュアルを読みながら、Lispの開発環境の新しい使い方や概念を知ったりもしましたが、まったく記事にもしておらず、自分自身忘れたりもしている始末。
また読み返すのも手間なので面白いものは記事にしておきたい所です。

普段のエディタも、会社の業務中に使っているエディタもLispWorksで、StumpWMもLispWorksで動かしているので、PCが起動している時間は、ずっとLispWorksを利用しているとは言えますが、コードは大して書いてないので、いまいちです。
まあ、この一年半ほどは、SBCL+SLIME+Emacsの三点セットを利用することなくLispWorksだけでなんとかできているので、そこそこ使えているとは言えるかもしれません。

来年やってみたいこと

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

加えて、「最強のIDEとしてのCommon Lisp」というお題を思い付いたので、この方向で何か探索してみたいです。

過去のまとめ


HTML generated by 3bmd in LispWorks 7.0.0

レトロLisp探検: PLASMA

Posted 2016-12-24 19:38:43 GMT

方言の系統

Planner

主要設計/開発者

Carl Hewitt、Russ Atkinson、Howie Shrobe、Marilyn McLennan

登場時期

1975年

特徴

アクターモデルの出発点となった処理系
Plannerの後継

後続への影響

ABCL/1、ABCL/R etc

概要

PLASMAは、PLAnner-like System Modeled on Actorsの頭字語だそうで、当初は、Planner-73と呼ばれていました。
PLASMAの大きな目的としては、プログラミング言語実装の基盤(理論)を提供することだったようです。

Micro-Planner、POP-2でのPlannerのフル実装、Conniverでの知見を元に、それらをアクターモデルとラムダ計算の枠組みで捉え直して構築したのがPLASMAでした。

なお、アクターモデルについては、パパートのLittle Peopleモデルや、アラン・ケイの初期SmalltalkについてのMITでのセミナーでの議論等に強く影響を受けているそうです。
また、アラン・ケイ経由でSIMULAからも影響を受けているとのこと。

PLASMAでは、全ての部品はアクターで、アクターはメッセージをうけとると何かをするもの、という定義です。
アクターはその振舞いによって定義されるもので、構成する部品から定義されるものではないとのこと。

プログラムはScriptと呼ばれ、より大きなアプリのような単位はScenarioと呼ぶなど、Actorだけになんとなく演劇用語がちりばめられているような気がしないでもありません。

大まかなコードの読み方

Lispのリストに相当する基本的なデータ構造は、リストを一般化したsequenceで、

[1 2 3 [4] 5]

のように記述します。

Plannerでも可能だったスプライス機能も存在し、unpackと呼ばれます。

;; x が [0 1 2 3] とする

[10 20 30 !x 40]
;=>
[10 20 30 0 1 2 3 40]

式は、Formと呼ばれ(..)で囲まれます。
Lispと同じく前置記法で全て記述できますが、可読性のため、特殊なシンボルが二番目に来た場合、特別な動作になります。コード例で前置記法を使った例は少なく、殆どはこの中置っぽい記法を利用しています。
また、数値の演算子も特別扱いされるので、中置で書かれることが多いようです。

=> <= -> <- == 

等が代表的な中置シンボルです。

リーダーマクロのように前置される記号がいくつかありますが、

  • quote: '
  • quasi-quote: "
  • unpack: !
  • binder: =
  • variable-binder: e=

となっています。この辺りを把握してあるとPLASMAのコードの大体は読めるようになります。

関数呼び出し

PLASMAではメッセージ送信で計算が進みますが、アクターがどうしたら良いか分からない場合は、NOT-APPLICABLEエラーとなります。

レシーバの書式ですが、

(≡> pattern body)

となっていて、パタンマッチが活用されます。
これは、Lispのlambdaと似ていて

(≡> [=x =y =z] body)

(lambda (x y z) body)

のような対応があります。

ざっくりとしたパタンマッチの書式ですが、

  • ? 何にでも
  • 'foo'foo
  • 識別子 はその値と同じものと
  • =識別子識別子が値と束縛として

マッチします。

[] の中身は 分解されてマッチしますが、[=x !=y][1 2 3 4]を与えれば、x1yは、[2 3 4]になります。
任意長の要素でマッチさせるには、!?を利用します。

また、caseが用意されていて、

(rules
  (≡> [] body0)
  (≡> [=hd !=tl] body1)
  (else body2))

のようにパタンで分岐できます。(なお初期は、rulesではなくcases)

PLASMAは書き方が多様なのですが、

(m => t)

(t <= m)

のどちら向きの矢印でも同じ意味になります。これが若干ややこしい。
また、通常のLisp的に書いても良く、

(fn x y z ...)

というのは、メッセージ送信で書くと

(fn <= [x y z ...])

となります。

各書法をまとめると、

(add-3 5) ;=> 8
(add-3 <= [5]) ;=> 8
([5] => add-3) ;=> 8
(5 => add-3) ;=> 8

といずれも同じ意味で多様すぎます。

関数定義

関数定義は、中置で

(add-3 ≡ (≡> [=x] (+ x 3)))

と書きますが、初期は、

(add-3 <- (=> [=x] (+ x 3)))

(define (add-3 =x) (+ x 3))

のように書いていて前置と中置どちらでも良かったようです。
前置の方はSchemeの先取り同じですね。

関数定義例

factorialが色々な書き方で出てくるのですが、

(factorial ≡ 
  (≡> [=n] 
    (rules n
      (≡> 1 1)
      (≡> (> 1) 
          (n * (factorial (n - 1)))))))

こういうのが簡潔な書式です。

メッセージ送信の記述には、低レベルなEnvelope Levelというものがあり、それで書くと、

(≡> [=n] (n + 1))

(≡≡> (request: [=n] (reply-to: =c))
     (c <== (reply: (n + 1))))

になるので、factorialも、Envelope Levelで記述すると、

(factorial ≡ 
  (≡≡> (request: [=n] (reply-to: =c))
    (rules n
      (≡> 1 (c <== (reply: 1)))
      (≡> (> 1) 
          (factorial <== 
            (request: [(n - 1)]
                      (reply-to: 
                       (≡≡> (reply: =y)
                            (c <== (reply: (y * n)))))))))))

こんなことになってしまいます。

また繰り返し的な記述として、末尾再帰のように書きますが、

(factorial ≡ 
  (≡> [=n] 
      ([1 n] =>
          (loop ≡ 
                (≡> [=acc =cnt]
                    (rules cnt
                           (≡> 1 acc)
                           (≡> (> 1)
                               (loop (acc * cnt) 
                                     (cnt - 1)))))))))

後のSchemeでお馴染みのTCO義務化のように、アクターモデルではリソースを消費しないもの、とされています。

このあたりについては、1973年のPlanner-73の時点で、繰り返しと再帰についての考察があり、末尾再帰のような形式は繰り返しとされています。

;; Planner-73
(label iterative-factorial
  (λ [n a]
     (if (n = 1)
         then a
         else (iterative-factorial (n - 1) (n * a)))))

(label recursive-factorial (λ [n a] (if (n = 1) then a else (n * (recursive-factorial (n - 1))))))

他、アクターと、副作用、繰り返し、コルーチン、データ構造、ジェネレータ、遅延構文、について色々ありますが、難しすぎるので興味のある方は文献を眺めてみてください。

後続のLispに影響を与えていそうなところ

Schemeは当初PLASMAを理解するために作られたということもあり、当然ともいえますが、なんといってもSchemeに多大な影響を与えているように思われます。
プログラミング言語の基盤となるものを提供する、という点もPLASMAと通底するものがあるかもしれません。

Conniverで提唱されたHairy Control Structureについて、PLASMAではアクターモデルの採用によって、Non-Hairy Control Structureを実現したとしています。
このあたりは、メッセージ送信、CPSあたりに通じていそうです。

Schemeの製作を通して、アクターと関数が同じものだということを発見した、という話がありますが、PLASMAではこれが統合されている節があり、Schemeからも逆輸入で影響を受けたようなことも書いてあるので、Planner-73→Scheme→PLASMAと影響が及んでいるのかもしれません。ややこしい。

PLASMAの初期では、ローカル関数は、==で定義していたのですが、後はletになりました。これは、LISP 1.5系のlabel的な構文では相互再帰が記述できないことに気付いたからだそうですが、後のlabelsletrecに繋がるのかなと思います。

他、ジェネレータ、遅延構文等でも影響を与えていそうです。

また、同じアクターモデルをベースとした言語にABCLがありますが、PLASMAに良く似た感じの文法になっています。
米澤先生の最終講義では、米澤先生がMIT時代、ヒューイット先生のアクターモデルに状態を持たせてみては、と提案したところ、ヒューイット先生曰くそれは全然違うものだ、となったので、それを並列オブジェクトモデルとしたそうです。

まとめ

今回は、Plasmaを紹介してみました。
アクター理論の話では良く出てくるPlasmaですが、名前が出てこなかったり、Actorsという名前で紹介されたり歴史的には大分ぼやけてしまっているようです。

動く処理系がないのは元より、言語仕様の入手も難しく、文法も難解で、動作を想像するのが難しいですが、Viewing Control Structures as Patterns of Passing Messagesあたりには詳しい解説があります。

参考文献


HTML generated by 3bmd in LispWorks 7.0.0

レトロLisp探検: Conniver

Posted 2016-12-23 21:57:44 GMT

方言の系統

Conniver

主要開発者

Drew V. McDermott、Gerald J. Sussman

登場時期

1972年

特徴

Plannerから続くAI向け言語。Lispをベースにデータベースとユーザーが操作可能な制御フローを持つ

後続への影響

Lisp Machine Lisp

概要

Conniverは、Plannerに影響を受けつつ、BobrowとWegbreitの制御機構(多分スパゲティスタック)や、Muddle(MDL)のオプショナル引数等を取り込み、さらにデータベースと、ジェネレータ等を持つLisp系言語です。

基本的にLispだそうですが、データを登録していく仕組みが組込まれていて、addや、removeで追加/削除できます。
それらのデータを元に、Possibilities Listを作成しデータを問合せたり制御を移したりしますが、そこにジェネレータが使われます。

関数の定義は、MACLISP系のdefunと似た感じのcdefunで、ジェネレータは、cdefgenで定義します。

MDLから影響を受けた、ラムダリストがあり、“OPTIONAL”、“REST”、“AUX”が使えます。なお“AUX”は、ラムダリスト内ではなくボディで利用。
MDLでは、“REST”ではなく、“TUPLE”でしたが、Common Lispの&restの祖先はConniverということになりそうです。

(cdefun foo ("optional" foo "rest" args)
  "aux" (x y z)
  ...)

は、Common Lispで書くと

(defun foo (&optional foo &rest args &aux x y z)
  ...)

になります。
letのネストが減るので、cdefunの“aux”は割合に良いかもしれないですね。

ボディ部は暗黙のprogになっていることが多くcdefunを始め、condの節まで暗黙のprogでタグを記述することができ、goで飛ぶことが可能です。

下記は、マニュアルにあるアイテムのデータベースへの登録/削除とジェネレータ呼び出しを含む例ですが、手続き型の制御機構を強化した印象が強いです。

(cdefun forcewin (player square)
  "aux" ((context (push-context context)))
  (add '(has ,player ,square))
  (remove '(free ,square))
  (makemove (other player))
  (return (try-next (winmoves player) nil)))

(cdefgen winmoves (player) "aux" (square1 p1 square2 p2 x) (csetq p1 (fetch '(has ,player ?square1))) :outerloop (try-next p1 '(adieu)) (csetq p2 (fetch '(has ,player ?square2))) :innerloop (try-next p2 '(go 'outerloop)) (cond ((lessp square1 square2) (cond ((csetq x (third-in-row square1 square2)) (cond ((present '(free ,x)) (note x))))))) (go 'innerloop))

Conniverで良く知られている機構に、Hairy Control Structureというものがありますが、Scheme方面では継続の操作として整理された所です。

呼び出した関数から途中で一度抜けて、残りを実行したりしていますが、

(cdefun printfoobar () "aux" (place)
  (cond ((csetq place (zowie))
         (go place))))

(cdefun zowie () (print 'foo) (return (tag 'printbar)) :printbar (print 'bar) nil)

(printfoobar) ;>> foo bar ;=> nil

Schemeだとこんな感じでしょうか(Scheme力が弱くていまいち)

(define (printfoobar)
  (define place (zowie))
  (place)
  (values))

(define (zowie) (call/cc (lambda (return) (print 'foo) (call/cc (lambda (printbar) (return printbar))) (print 'bar) (lambda () #f))))

(pirntfoobar) ;>> foo ;>> bar

ConniverからベースのLisp処理系の関数は任意に呼び出せますが、LispからConniverの関数を呼ぶには、@構文等を使いる等作法があるようです。
Micro-Plannerでもそんな感じでしたが、今でいうとShenが似たような感じかなあと思います(LispからShenを呼ぶ方法は用意されていないですが)

後続のLispに影響を与えていそうなところ

closure構文がありますが、これはLisp Machine Lispのclosure構文と似た感じです。しかし、取り込む変数の指定がないので、環境全部を取り込むのかもしれません。

また、関数と変数の文脈の曖昧さ/意図せぬシャドウを無くすためにcallがありますが、MACLISPのfuncallはこれに影響を受けて導入されたのかもしれません。
もしそうならMACLISPにもcallという名前そのままで導入して欲しかったかも。

体験

Sail Dartにソースらしきものがありますが新しめのMACLISPでは動きませんでした。

是非、誰か動かしてみて下さい……。

まとめ

今回は、Conniverを紹介してみました。
Planner→Conniver→Scheme→Racketと続く命名シリーズですが、Conniverはちょっと影が薄いかもしれないですね。
ただ、多様な制御機構については、Schemeに受け継がれていった気はします。

参考文献


HTML generated by 3bmd in LispWorks 7.0.0

レトロLisp探検: Planner

Posted 2016-12-23 13:42:04 GMT

方言の系統

Planner

主要設計者/開発者

Carl Hewitt、Gerald J. Sussman、Terry Winograd

登場時期

1967年

特徴

Lisp上に構築された論理型言語の先駆け、もしくはLispの論理型言語拡張

後続への影響

Conniver、QA4、Plasma、MDL

概要

Plannerは、1967年頃から構想が始まった論理型言語です。
ロボットが行動するプランを記述できるような言語として構想が始まったようですが、当時、全てのアイデアをそのまま実装することはできず、1970年頃にGerald J. Sussman、Terry Winograd氏によってMicro-Plannerとして実装されます。
このMicro-Plannerを用いて記述されたのが、ロボットアームで積み木を操作する、SHRDLUで、この時代に大きなインパクトを残しました。
むしろPlannerよりSHRDLUの方が有名な位です。

PlannerはPrologと比較されることが多く、その文脈では、どうしても後の論理型言語の感覚で捉えられがちな気がしますが、オリジナルの論文では、宣言型プログラミングをLispに導入したような面も大きいようで、後世のLisp処理系のProlog拡張のような雰囲気もあります。
Prologでは推論は後ろ向きのみですが、Plannerでは、推論は前向きと後ろ向きの両方を利用します(Prologにも前向き推論の拡張はありますが)

Plannerのコードは、こんな感じですが、

(assert (human turing))

(define theorem1 (consequent (x) (fallible $?x) (goal (human $?x))))

(goal (human turing))

同じものをMicro-Plannerで記述すると、

(thassert (human turing))

(defprop theorem1 (thconse (x) (fallible $?x) (thgoal (human $?x))) theorem))

(thgoal (fallible turing) (thtbf thtrue))

となり、Micro-Plannerの方は、接頭辞にthが付きまくるのと、当時のMACLISPの書法が入り乱れることもあり謎言語感が倍増します。

以降、基本的にオリジナルのPlannerについて書いていきます。

Plannerは、パタンマッチにMatchlessというサブ言語を利用していますが(Micro-Plannerでは、Matcher)、これがかなり強力なもので、拡張されたLispのような言語です。
関数と、actorがあり、それぞれ、lambdakappaで記述されるのですが、actorの方は、後に有名になるアクタ理論とは違うようで、値を返してくるのが関数、マッチした結果を返すのがactorということのようです。

関数とactorは混交して記述されますが、関数の定義は、下記のような感じです。

(define super-reverse 
  (lambda (x)
    (cond ((is (atomic) $$x) $$x)
          (t (<super-reverse (rest $$x)> (super-reverse (1 $$xx)))))))

Common Lispで書くと、こんな感じです。

(defun \1 (list) (car list))
(defun atomic () #'atom)
(defun is (x y) (funcall x y))

(defun super-reverse (x) (cond ((is (atomic) x) x) (t `(,@(super-reverse (rest x)) ,(super-reverse (\1 x))))))

(super-reverse '(1 2 (3 (4)) (5 ((((6)))) 7 ))) ;=> ((7 ((((6)))) 5) ((4) 3) 2 1)

数字が要素を取得する関数のように機能し、atomicは多分actor、isはactorがマッチしたかを調べるフォームで、<>で呼び出されたフォームは呼んだ場所にスプライシングされます。

下記は、手続き的な繰り返しforを利用した例、

(define factorial 
  (lambda (fix) (((fix) n))
    (for (fix) 
           (((fix) (temp 1)))
           ((test (is (less 2) $$n) (return $$temp)))
           (step (assign $←n (- $$n 1)))
      (assign $←temp (* $$n $$temp)))))

lambdaや、kappa等、変数宣言があるフォームには返り値と変数の型を記述することが可能で、ラムダリストの一つ前は返り値の型の宣言です。

kappaの例が出てきていないので一応書いておくと、※(; ...)がコメントです。

((kappa (fix) (((fix) x)) $$x) (+ 2 2)) 
(; => 4)

のようになります。
fixはfixnumですが、型は(fix)のように書きます。また変数参照は、$$varと書きます。
上記の場合、fixnumの場合だけマッチするactorということになります。

どうもlambdakappaもラムダリストの括弧が一階層多い気がするんですが、引数全体でリストということなのかもしれません(後継のPlasmaもこんな感じです)

まだまだ多数の機能がありますが、Matchlessは、このほかにマルチプロセスや、コルーチンの為の構文等、様々な機能があり、上層のPlannerからそのまま使えるようです。

後続のLispに影響を与えていそうなところ

Lispの関数/変数名のハイフン繋ぎはいつから始まったのか割合に謎なのですが、1967年のPlannerの文献には、まだ登場せず、1968年のPlannerのマニュアルには頻出することから、Planner周辺りが起源なのかなと予想しています。

また、コメントが(; ...)ですが、形式は違うもののセミコロンがコメントに採用されたのはPlannerの影響なのかもしれません。

型宣言の辺りは、MDLに影響を与えていそうで、MDLから更にLisp Machine Lisp、Common Lispあたりに繋がりそうです。

体験

Common Lisp版のSHUDLUのソースが配布されていますが、その中にMicro-Plannerが潜んでいます。
抜き出して試してみたりしていますが、どうも上手く使えません。
今後上手く使えたらまた記事にでもしてみたいと思います。

オリジナルのPlannerの実装は、Poplerとして、エジンバラ大学のpop-2で実装されたようですが、poplogに繋がる流れのようなので、どこかに動かすことができる環境が残っているかもしれません(が不明)

まとめ

今回は、Plannerを紹介してみました。
Plannerという言語を構成する個々の部品を眺めても、それぞれ画期的なアイデアが満載です。
オリジナルのPlannerの論文に書かれたアイデアは時代の先を行き過ぎている感がありますが、Hewitt先生は大天才なんだなあと思った次第。

参考文献


HTML generated by 3bmd in LispWorks 7.0.0

レトロLisp探検: EuLisp

Posted 2016-12-22 14:43:43 GMT

方言の系統

Common Lisp、Scheme

主要開発者

Jérôme Chailloux、Julian Padget

登場時期

1990年頃

特徴

Common Lispを綺麗にしたような言語仕様のLisp

後続への影響

ISLISP

概要

EuLispは、1985年にLe-Lispで知られるJérôme Chailloux氏による次世代Lisp策定の呼び掛けからスタートした方言です。
Euroのユーで、”ユーリスプ”と読むらしいですが、政治的な問題で、EuroのEuではなく、ギリシャ語の”良い”という意味とされたとのこと。

Common Lispの大きく過去との互換性のしがらみで見通しが悪い仕様に対して、小さくすっきりとした設計でかつ拡張性もあるようなものとして構想されたようですが、当時米国だけで決められていると思われていたCommon Lispに対し、少からぬ反発がヨーロッパあたりにはあったようです。 (ちなみにCommon Lispを始めた人達も国際的に反発を招く程、Common Lispが大きな流れになるとは予想だにしないことだったようです)

1985年当初の案では、全体を

  • Level-0 (The Semantic Foundation)
  • Level-1 (The Kernel)
  • Level-2 (The Development Language)

の3つのレイヤーとし、核を中心に言語を利用形態に応じて拡張できるような設定だったようです。

Level-0の仕様は表示的意味論で記述され、Common Lispでいうスペシャルフォームや基本的な型、オブジェクト指向システムのコアは、Level-0で定義されます。
複雑なラムダリスト等もここでは定義されず、必須引数のみ、とのことです。
また、スペシャル変数等もLevel-0ではサポートしません。

Level-1は、Common Lispの基本的な関数が一通りある位の規模で、動的変数のサポート、複雑なラムダリスト、クラスの多重継承等をサポートします。

Level-2は、Common Lisp全体と同程度の規模を実現するもので、様々な組み込み関数が用意されます。

以上、3つのレイヤーが構想されていたようですが、どうやらLevel-2は途中で構想から外れてしまったようで、現在入手できる仕様はLevel-0と、Level-1のみの構成のようです。

コードの見た目は、殆どMACLISP系というかCommon Lispそのままですが、大きく違うのは、EuLispはLisp-1ということで、Level-0だけみるとCommon LispよりSchemeに近く、ISLISPくらいのものになっています(というかISLISPがEuLispに多大な影響を受けている)

モジュール機能も装備されていますが、これはTのlocaleのような感じで、モジュール構文のスコープの中に記述します。

(defmodule fib
  (syntax (syntax-0)
   import (level-0))

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

(defun fib-iter (n a1 a2) (cond ((= 0 n) a2) ((= 1 n) a1) (t (fib-iter (- n 1) (+ a1 a2) a1))))

(export fib fib-iter))

動的変数は、Common Lispのように変数に特殊な宣言したスペシャル変数のようなものではなく、ISLISPと同じくdynamic構文を使ってアクセスする形式です。
恐らくこれは、ISLISPが取り込んだのでしょう。

オブジェクト指向システムは、Common LispのCLOSに対し、ΤΕΛΟΣ(TELOS)というもので名前がなんか格好良いですが、大体Common Lispと同じです。
キーワードが後置のコロンでSmalltalkっぽいのと、Lisp-1なので名前の競合を防ぐ為に<クラス名>と書く作法が見た目で違う所でしょうか。

(defmodule g1
  (syntax (syntax-0)
   import (level-0))

(defclass <bank-account> () ((dollars default: 0 accessor: dollars. keyword: dollars:)))

(defgeneric account-dollars ((a <bank-account>)) (dollars. a))

(defgeneric deposit ((a <bank-account>) (n <number>)))

(defmethod deposit ((a <bank-account>) (n <number>)) ((setter dollars.) a (+ n (dollars. a))))

(defgeneric withdraw ((a <bank-account>) (n <number>)))

(defmethod withdraw ((a <bank-account>) (n <number>)) ((setter dollars.) a (max 0 (- (dollars. a) n)))))

ちなみに、Common Lispだと、クラスオブジェクトではなくシンボルに対して操作することが殆どなので、<class>ではなく、'<class>というシンボルを扱うことになってしまい、ちょっと格好悪いのですが、好きな人は好んでこの書き方をするようです。

ISLISPも、Lisp-2ですが、このクラスの命名規約になっているのがちょっと不思議です。しかし、クラス名にクォートが必要な場所やフォームは悉くスペシャルフォームとなっていてクォートは書かなくても良いようになっています。Lisp-1でもLisp-2でも書けるようにするためだったのか、それとも後々まで仕様が確定しなかったためなのか、ちょっと謎を追い掛けてみたいものがあります。

体験

EuLispは何種類か実装があるようですが、YouToo、EuxLisp、Eu2Cがまとめられて配布されています。
どれでもLevel-0は実装されているようですが、自分が試したところでは、Level-1はどれも思ったように動かせていません。

まとめ

今回は、EuLispを紹介してみました。Common Lispよりモダンで次世代のCommon Lispの仕様を決めるなら、EuLispを核にしたら良さそうですが、世の中の流れ的にそうなりそうな気配は一切ないですね。

EuLispはISOのLispになるべく動いていたようですが、最終的には、Common LispとEuLispを足して2で割ったようなISLISPになったようです。

個人的には、EuLispがそのままISO LISPになったら、もうちょっとインパクトがあったかなあと思います。 (もしくは、ANSI Common LispがそのままISO Common Lispになるとか)

参考文献


HTML generated by 3bmd in LispWorks 7.0.0

Older entries (2053 remaining)