before lisps just part of the past #3 ~ irritating packages ~
TRANSCRIPT
BeforeLISPs
Just Part of the Past
〜 #3 irritating Packages 〜
PackageとSymbol● PackageはSymbolの集合
● 関数の集合だとか変数の集合という考え方はしない。ひたすらSymbolの集合。
● Symbolはいろいろな物を持っていて、関数として使わ
れた場合は関数を返し、グローバル変数として使われた場合はグローバル変数の値を返す
– (list 0 1 2)と(funcall (symbol-function 'list) 0 1 2))はだいたい同じ
Symbol
FOO package
IOTAFLATTENGROUP-HEADED…
> (iota 3) => (0 1 2)> iota => 72
symbol-function(lambda (n)(loop for i from 0 upto (1- n) collect i))
symbol-value72
symbol-package#<PACKAGE “FOO”>
symbol-name“IOTA”
symbol-plist()
Symbol
BAR package
ALPHABETA...IOTA…
> (iota 3) => NIL> iota => “i”
symbol-function(lambda (str) (and (stringp str) (string= “i” str)))
symbol-value“i”
symbol-package#<PACKAGE “BAR”>
symbol-name“IOTA”
symbol-plist()
Current Package
BAR package
ALPHABETA…IOTA...
FOO package
IOTAFLATTENGROUP-HEADED…
カレントパッケージがFOOの場合
> (iota 3) => (0 1 2)カレントパッケージがBARの場合
> (iota 3) => NIL
カレントパッケージは*PACKAGE*の値で制御
カレントパッケージの切り替えはIN-PACKAGEで行うのが無難
例) (in-package :chat-with-tree)
DEFPACKAGE● パッケージを定義する● 他のパッケージに所属しているシンボルで使いたいシンボルがあったらその
パッケージをUSEするか、シンボルを直接指定してIMPORTする。
● (defpackage :chat-with-tree (:use :cl :chiku.util))
● これで、カレントパッケージがCHAT-WITH-TREEの場合には
CHIKU.UTILパッケージの外部シンボルがすべて使える
● (defpackage :chat-with-tree (:use :cl) (:import-from :chiku.util :aif :it))– これはCHIKU.UTILパッケージから2つのシンボル、AIFとITだけ使う場合
今日の主題はPackage入門ではありません
参考書
● パッケージの説明はPractical Common Lispの21章が素晴らしい
– 邦訳版:実践Common Lisp● ただし、Practical Common Lispはシンボルの説明
があんまりわかりやすくないというか、まとまっていない
● シンボルの説明はANSI Common Lispの第8章がよく
まとまっている
– 邦訳版:ANSI Common Lisp
ズボラなDEFPACKAGE(defpackage :chiku.register (:use :cl :chiku.util :sqlite :cl-fad :split-sequence :sb-ext))
どのパッケージからどんなシンボルを使ってるのかがさっぱりわからない → 読む人にやさしくない
律儀なDEFPACKAGE
● 書くのが面倒
● 以下の2箇所で意思表示しなくちゃいけない
– DEFPACKAGE– 実際の使用
(defpackage :chiku.register (:import-from :common-lisp :defpackage :in-package :defparameter :merge-pathnames :defun :cadr :position :subseq :car :list :string= :ignorable :declare :quote :stringp :if :&rest :destructuring-bind :&body :defmacro :defgeneric :defclass :defmethod :call-next-method :append :t :make-instance :length :- :make-list :zerop :remove-if :gethash :push :dolist :equal :function :make-hash-table :let :lambda :mapcar :nreverse :reduce :maphash :rename-file :delete-file :apply :mapc :namestring) (:import-from :chiku.util :str<-textfile :concat-str :once-only :str-case :_ :papply :itoa :drop) (:import-from :sqlite :execute-single :disconnect :execute-to-list :execute-non-query :connect) (:import-from :split-sequence :split-sequence) (:import-from :cl-fad :pathname-as-directory :copy-file) (:import-from :sb-ext :*posix-argv* :run-program))
問題点
● 2箇所以上で意思表示するのはかなりまずい– 修正が難しいから、そして面倒だから
● ロジックを記述している瞬間はどのシンボルを使ってるのか理解しているはずだから、実際の使用が優先されるべき– どのシンボルを使っているのか履き違えている場合は、ロ
ジックを正しく記述できない
● シンボルはオブジェクトで、パッケージもオブジェクト、つまりLISPで扱える。
→ズボラver.から律儀ver.を生成できる
ズボラver.から律儀ver.を作る
● シンボルは自分の出自を知っているので、実装はすごく簡単● ソースコードから式を読み、シンボルだけ取り出して各シンボルの所属
パッケージを調べる
– READ– FLATTEN, REMOVE-IF-NOT, REMOVE-DUPLICATE– SYMBOL-PACKAGE
● 手書きの時は、インターンされずかつ書きやすいKeyword Symbolが手軽だが、自動生成なら文字列が便利
– SYMBOL-NAME, PACKAGE-NAME– Keyword Symbolの方がかっこいいという感性の人は、KEYWORDパッ
ケージへINTERNすればOK
ちょっとした問題
● 新しく起動したREPLで単にファイルをLOADして律儀ver.の生成、とやっても
うまくいかない
● つまり、よそから持って来たライブラリのDEFPACKAGEがズボラver.で書か
れていても、単にLOADして律儀ver.の出力、とやってもうまく行かない。
● READはシンボルをカレントパッケージにインターンしようとする
– カレントパッケージを変更した上で出力すれば問題ない
– (load “sample.lisp”)(let ((*package* (find-package :chiku.register))) (recompose-defpackage :chiku.register “sample.lisp”))
ズボラのススメ
● ソースコードは静的なものなので、この結果を貼りこむのは本質的に手動
– LISPオブジェクトじゃない、LISPの外という意味
– 公開するソースコードのDEFPACKAGEフォームをこのやり方で生成して貼
りこむ、という使い方自体はできる
● むしろ、ズボラver.以外書かない方針を提唱したい
– 律儀ver.は簡単な手順で生成できる
● LISPに聞いてわかることをわざわざ書かない、書かせない
● ソースコード(読む用の)もテキストじゃなくLISPオブジェクトとして
配布できたらいいと思う。– 危ないけど
その他の細々したこと
● あるパッケージをIN-PACKAGEするファイルが複数ある場合
● IN-PACKAGEが書かれていないファイルがある場合
● 1つのファイルでIN-PACKAGEが複数回行われている場合
● LOADが好ましくない副作用を持つ場合
● ズボラと律儀の間にあるDEFPACKAGE● EXPORT節などの取り扱い
● 神は細部に宿ります