#:g1: frontpage

 

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

Older entries (2061 remaining)