#:g1: frontpage

 

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

Older entries (2112 remaining)