#:g1

com.informatimago.common-lisp.lisp-textの紹介

Posted 2014-11-29 15:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の334日目です。

com.informatimago.common-lisp.lisp-textとはなにか

 com.informatimago.common-lisp.lisp-textは、Pascal Bourguignon氏作のCommon Lispのソースコードを操作するためのライブラリです。

パッケージ情報

パッケージ名com.informatimago.common-lisp.lisp-text
Quicklisp

インストール方法

(ql:quickload :com.informatimago.common-lisp.lisp-text)

試してみる

 主な関数は、source-readで、ファイルから読み込んだ定義をソースオブジェクトとして返します。
定義や、コメント、オブジェクト等はそれぞれ別のクラスになって格納されるので、編集操作が簡単に可能です。

 試しにあるシステムから最長のdefunを抜き出すという関数を作ってみます。

(defpackage :source-text-demo
  (:use :cl)
  (:import-from :com.informatimago.common-lisp.lisp-text.source-text
                :source-read
                :source-object-text))

(cl:in-package :source-text-demo)

(defun system-sources (system) (loop :for file :in (mapcar #'asdf:component-pathname (asdf:component-children (asdf:find-system system))) :append (with-open-file (in file) (loop :for item := (source-read in nil in) :until (eq item in) :collect item))))

(defun find-the-longest-defun-form (src) (flet ((count-lines (text) (count #\Newline text))) (let* ((items (copy-list src)) (text (source-object-text (first (sort (remove-if-not (lambda (x) (and (source-object-text x) (*:scan "^\\(defun" (source-object-text x)))) items) #'> :key (lambda (x) (count-lines (source-object-text x)))))))) (format T "~&;;;~%;;; ~D line~:*~P~%;;;~%~A" (count-lines text) text))))

実行

(find-the-longest-defun-form (system-sources :contextl))
;>>  ;;;
;>>  ;;; 34 lines
;>>  ;;;
;>>  (defun ensure-layered-function
;>>         (name
;>>          &rest initargs
;>>          &key (lambda-list () lambda-list-p)
;>>          (argument-precedence-order (required-args lambda-list))
;>>          (documentation nil)
;>>          (generic-function-class 'layered-function)
;>>          &allow-other-keys)
;>>    (unless lambda-list-p
;>>      (error "The layered function ~S must be initialized with a lambda list." name))
;>>    (let ((gf (let ((layer-arg (gensym "LAYER-ARG-")))
;>>                (apply #'ensure-generic-function
;>>                       (lf-definer-name name)
;>>  		     :generic-function-class
;>>                       generic-function-class
;>>                       :argument-precedence-order
;>>                       `(,@argument-precedence-order ,layer-arg)
;>>                       :lambda-list
;>>                       `(,layer-arg ,@lambda-list)
;>>                       (loop for (key value) on initargs by #'cddr
;>>                             unless (eq key :documentation)
;>>                             nconc (list key value))))))
;>>      (setf (fdefinition name)
;>>            (let ((lambda `(lambda (&rest rest)
;>>                             (declare (optimize (speed 3) (debug 0) (safety 0)
;>>                                                (compilation-speed 0)))
;>>                             (apply (the function ,gf)
;>>                                    (layer-context-prototype *active-context*)
;>>                                    rest))))
;>>              #-ecl (compile nil lambda)
;>>              #+ecl (coerce lambda 'function)))
;>>      (when documentation
;>>        (setf (documentation name 'function) documentation))
;>>      (bind-lf-names name)
;>>      gf))
;=>  NIL

まとめ

 今回は、com.informatimago.common-lisp.lisp-textを紹介してみました。コメントを削除したり、定義間の行間を変更したりも簡単にできますし、応用次第では結構色々できそうです。

common-idiomsの紹介

Posted 2014-11-27 15:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の332日目です。

common-idiomsとはなにか

 common-idiomsは、Brian Mastenbrook氏作のCommon Lispのユーティリティ集です。

パッケージ情報

パッケージ名common-idioms
Quicklisp×
CLiKiCLiki: common-idioms

インストール方法

 CLiKiのリンクも死んでいますが、archive.orgでファイルを探すか、common-idioms-3.tar.gzでググって適当にソースを探します。ASDFで読み込めるので、Quicklispのlocal-projectsに置けばQuicklispでロード可能です。

  • ftp://tux.rainside.sk/gentoo/distfiles/common-idioms-3.tar.gz
(ql:quickload :common-idioms)

試してみる

 定義されているユーティリティは下記の通りです。

  • aif
  • aif2
  • destructuring-case
  • expand-only
  • fconstantly
  • it
  • let-env
  • let-env*
  • macroexpand-n
  • macrolet*
  • map1
  • reducen
  • run-tests
  • setf-it
  • sif
  • symbolic
  • with-gensyms

 適当につらつらと紹介していきたいと思いますが、else節でsetf-itというのが使えるのが面白いaif

(let ((xs (list nil 2)))
  (aif2 (car xs)
      it
      (setf-it 42))
  xs)
;=>  (42 2)

(let ((tab (*:alist-hash-table '((a . 0) (b . 1))))) (aif2 (gethash 'c tab) it (setf-it 42)) (*:hash-table-alist tab)) ;=> ((C . 42) (B . 1) (A . 0))

ここまで複雑だとパタンマッチマクロの方が使いやすそうなdestructuring-case

(destructuring-case next (1)
  (((1 (y (z))) (list 1 y z))
   ((x (y (z))) (list z y x)))
  '(1 (2 (3)))
  :else)
;=>  (1 2 3)

(destructuring-case next (1) (((1 (y (z))) (list 1 y z)) ((x (y (z))) (list x y z))) '(1 (2 (3 8))) :else) ;=> :ELSE

展開するフォームを指定できるexpand-only

(expand-only '()
             '(defun foo (n)
               (when n
                 (list n))))
;=>  (DEFUN FOO (N) (WHEN N (LIST N)))
;    NIL

(expand-only '(when) '(defun foo (n) (when n (list n)))) ;=> (DEFUN FOO (N) ; (IF N ; (PROGN (LIST N)) ; NIL)) ; NIL

多分マクロの中でgensymのリストを作るのに使うfconstantly

(mapcar (fconstantly #'gensym) '(a b c))
;=>  (#:G1915 #:G1916 #:G1917)

レキシカルスコープを曲げるlet-env/let-env*

(let ((x 3))
  (let-env e0
    (let ((x 4))
      (e0 (x) (list x)))))
;=>  (3)

(let ((x 3) (y 4)) (let-env* e0 (x y) (let ((x 4)) x (e0 (list x y))))) ;=> (3 4)

let-env*とmacroletを合体して、let-env*の環境を持ち運ぶマクロに任意の名前を付けられるようにしたmacrolet*

(let ((x 3))
  (macrolet* (x) ((foo (x) x))
    (let ((x 2))
      (foo x))))
;=>  3

ところで、bend-lexicalという表現の大元は、The Scheme Programming Language, 2nd Edition(Kent Dybvig)なんでしょうか。

その他、多値関数でreduceするreducen、指定した回数展開するmacroexpand-n等があります。

まとめ

 今回は、common-idiomsを紹介してみました。
十年前は大活躍していたBrian Mastenbrook氏ですが、この5、6年位はLisp的な活動はしていないようです。
色々活躍していただけに残念ですね。

com.informatimago.common-lisp.lisp.stepperの紹介

Posted 2014-11-26 15:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の331日目です。

com.informatimago.common-lisp.lisp.stepperとはなにか

 com.informatimago.common-lisp.lisp.stepperは、Pascal Bourguignon氏作のポータブルなステップ実行のユーティリティです。

パッケージ情報

パッケージ名com.informatimago.common-lisp.lisp.stepper
Quicklisp

インストール方法

(ql:quickload :com.informatimago.common-lisp.lisp.stepper)

試してみる

Common Lispの標準にもstepというステップ実行の為のユーティリティは存在するのですが、SBCLやCCLのようにコンパイル指向の場合は、実行時にソースがないのでstepを実行しても素気ない感じで終わります。
(defun fib (n)
  (if (< n 2)
      n
      (+ (fib (1- n))
         (fib (- n 2)))))

(step (fib 1))

Evaluating call: (FIB 1) With arguments: 1 [Condition of type SB-EXT:STEP-FORM-CONDITION]

Restarts: 0: [STEP-CONTINUE] Resume normal execution 1: [STEP-OUT] Resume stepping after returning from this function 2: [STEP-NEXT] Step over call 3: [STEP-INTO] Step into call 4: [RETRY] Retry SLIME interactive evaluation request. 5: [*ABORT] Return to SLIME's top level. --more--

;⌨ -> 0 ;=> 1

 com.informatimago.common-lisp.lisp.stepperを使えば、コンパイル指向の処理系でもソースを追い掛けた感じのステップ実行が可能です。
可能ですが、ただし専用のパッケージ内で関数を定義する必要があります。

(defpackage :stepper-demo
  (:use :stepper))

(cl:in-package :stepper-demo)

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

という定義をして、stepを実行

(stepper:step (fib 1))

(Will evaluate (fib 1) Step Into (s, si, RET), Step over (so), Trace (t), Function (f), Run (r), List (l), Eval (e), Debugger (d), Abort (a, q)? ;⌨ -> RET Will evaluate (fib 1) (Will evaluate 1 Step Into (s, si, RET), Step over (so), Trace (t), Function (f), Run (r), List (l), Eval (e), Debugger (d), Abort (a, q)? ;⌨ -> RET (--> 1)) (Entering function fib (Bind n to 1) Step Into (s, si, RET), Step over (so), Trace (t), Function (f), Run (r), List (l), Eval (e), Debugger (d), Abort (a, q)? ;⌨ -> RET (Will evaluate (if (< n 2) n (+ (fib #) (fib #))) Step Into (s, si, RET), Step over (so), Trace (t), Function (f), Run (r), List (l), Eval (e), Debugger (d), Abort (a, q)? ;⌨ -> RET Will evaluate (if (< n 2) n (+ (fib #) (fib #))) (Will evaluate (< n 2) Step Into (s, si, RET), Step over (so), Trace (t), Function (f), Run (r), List (l), Eval (e), Debugger (d), Abort (a, q)? ;⌨ -> RET Will evaluate (< n 2) (Will evaluate n Step Into (s, si, RET), Step over (so), Trace (t), Function (f), Run (r), List (l), Eval (e), Debugger (d), Abort (a, q)? ;⌨ -> RET (n ==> 1)) (Will evaluate 2 Step Into (s, si, RET), Step over (so), Trace (t), Function (f), Run (r), List (l), Eval (e), Debugger (d), Abort (a, q)? ;⌨ -> RET (--> 2)) Evaluation of (< n 2) returned one result ==> T) (Will evaluate n Step Into (s, si, RET), Step over (so), Trace (t), Function (f), Run (r), List (l), Eval (e), Debugger (d), Abort (a, q)? ;⌨ -> RET (n ==> 1)) Evaluation of (if (< n 2) n (+ (fib #) (fib #))) returned one result ==> 1) Exiting function fib returned one result ==> 1) Evaluation of (fib 1) returned one result ==> 1)

 ちょっとみづらい気がするので改造してみますが、

(defun stepper::will-step (form &optional (stream *step-trace-output*))
  (with-step-printing
    (let ((pos (sb-kernel:charpos stream)))
      (format stream
              "Will evaluate ~&~VT⎛~&~VT⎜  ~S~&~VT⎝~%"
              pos
              pos
              form
              pos))))
(Will evaluate 
 ⎛
 ⎜  (fib 1)
 ⎝
 Step Into (s, si, RET), Step over (so), Trace (t), Function (f), Run (r), List (l), Eval (e), Debugger (d), Abort (a, q)?
;⌨ -> RET
Will evaluate 
 ⎛
 ⎜  (fib 1)(Will evaluate 
  ⎛
  ⎜  1
  ⎝
  Step Into (s, si, RET), Step over (so), Trace (t), Function (f), Run (r), List (l), Eval (e), Debugger (d), Abort (a, q)?
;⌨ -> RET
  (--> 1))
 (Entering function fib
   (Bind n                to 1)
  Step Into (s, si, RET), Step over (so), Trace (t), Function (f), Run (r), List (l), Eval (e), Debugger (d), Abort (a, q)?
;⌨ -> RET
  (Will evaluate 
   ⎛
   ⎜  (if (< n 2) n (+ (fib #) (fib #)))
   ⎝
   Step Into (s, si, RET), Step over (so), Trace (t), Function (f), Run (r), List (l), Eval (e), Debugger (d), Abort (a, q)?
;⌨ -> RET
Will evaluate 
 ⎛
 ⎜  (if (< n 2) n (+ (fib #) (fib #)))(Will evaluate 
    ⎛
    ⎜  (< n 2)
    ⎝
    Step Into (s, si, RET), Step over (so), Trace (t), Function (f), Run (r), List (l), Eval (e), Debugger (d), Abort (a, q)?
;⌨ -> RET
Will evaluate 
 ⎛
 ⎜  (< n 2)(Will evaluate 
     ⎛
     ⎜  n
     ⎝
     Step Into (s, si, RET), Step over (so), Trace (t), Function (f), Run (r), List (l), Eval (e), Debugger (d), Abort (a, q)?
;⌨ -> RET
     (n ==> 1))
    (Will evaluate 
     ⎛
     ⎜  2
     ⎝
     Step Into (s, si, RET), Step over (so), Trace (t), Function (f), Run (r), List (l), Eval (e), Debugger (d), Abort (a, q)?
;⌨ -> RET
     (--> 2))
    Evaluation of (< n 2) returned one result ==> T)
   (Will evaluate 
    ⎛
    ⎜  n
    ⎝
    Step Into (s, si, RET), Step over (so), Trace (t), Function (f), Run (r), List (l), Eval (e), Debugger (d), Abort (a, q)?
;⌨ -> RET
    (n ==> 1))
   Evaluation of (if (< n 2) n (+ (fib #) (fib #))) returned one result ==> 1)
  Exiting  function fib returned one result ==> 1)
 Evaluation of (fib 1) returned one result ==> 1)

という風に評価される部分で一歩ずつ止まります。

 専用のパッケージで定義する必要があるということから大体想像が付きますが、com.informatimago.common-lisp.lisp.stepperではソースを保存するために必要となる関数にステップ実行の為の仕掛けを入れます。

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

;==> (stepper:defun fib (n) (stepper:if (cl:< n 2) n (cl:+ (fib (cl:1- n)) (fib (cl:- n 2)))))

まとめ

 今回は、com.informatimago.common-lisp.lisp.stepperを紹介してみました。ステップ実行が苦手な処理系を使っていると、stepも使わなくなりますが、これならデバッグ時に利用できるかもしれないですね。

MIT FORMATの紹介

Posted 2014-11-25 15:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の330日目です。

MIT FORMATとはなにか

 MIT FORMATは、MIT系Lispでお馴染のFORMATです。

パッケージ情報

パッケージ名MIT FORMAT

インストール方法

 LispマシンのソースやMacLISPのソースからひっこ抜いてきて動かします。
Lispマシン版は、LMI LambdaのものをCommon Lispに移植してみたものがありますので興味があったらどうぞ。

試してみる

 MIT系Lispの機能で魔窟と化しているものの代表例として、FORMATとLOOPがあります。これら以外だとDEFSTRUCTとラムダリストもそうでしょうか。
拡張を繰り返して謎の機能を盛り込み、最終的にはユーザーが拡張できるようになることが多いようなのですが、Common Lispが出た頃には既に収束していたようです。
大別するとMacLISPのFORMATとLispマシンのFORMATがあるのですが、どちらも機能的には大体同じです。
MacLISP版の方は、MacLISP/NIL/Zetalispのスーパーセットのような方言でかつ、文芸的プログラミングのようにも書けて、コードからドキュメントも生成されるというLSBというフォーマットで書かれています(MIT/LCS/TM-200参照のこと)

 Common LispのFORMATと大体のところは同じですが、DEFFORMATでユーザーが拡張できます。
一文字の場合は、~文字 で呼び出せて、複数文字の場合は、~\名前~\ で呼び出せます。
Common Lispでも~//でユーザー定義の関数を利用可能なので、Common Lispでできないことといえば、一文字の指示子が定義できる位でしょうか(MacLISP/Zetalispではエスケープが\なので/と入れ替わっています。)

 defformatで漢数字を表示する指示子を作ってみると、

(knum (random (expt 10 63)))
;=>  "三百七十一那由他四千九百五十一阿僧祇二千七百九十八恒河沙三千九百七十六極九千三十九載四千三百七正二千七百六十九澗八千八百八十一溝六千九十一穣八千五百六十七𥝱八千五百九十五垓四千二百四十一京千八百九十九兆四千九百八億千六万四千九百十一"
;; のようなものが予め定義されているとする

(defun knum-format (arg params)
  (declare (ignore params))
  (princ (knum arg) *standard-output*))

(lambda.format:defformat(:one-arg) knum-format)

(lambda.format:format t "~D = ~:*~数" (random (expt 10 63)))
;>>  729257794194361409722610781525163841524812490803370431050961803 = 七百二十九那由他二千五百七十七阿僧祇九千四百十九恒河沙四千三百六十一極四千九十七載二千二百六十一正七百八十一澗五千二百五十一溝六千三百八十四穣千五百二十四𥝱八千百二十四垓九千八十京三千三百七十兆四千三百十億五千九十六万千八百三
;=>  NIL

のような感じでしょうか。上記では、ユーザーが登録できると説明しましたが、*FORMAT-CHAR-TABLE*に指示子の文字を登録しないといけないので、一文字の指示子は、あまりユーザーが気軽に定義するものでもないのかもしれません。
defformatでは、~{ ~}のような繰り返し構文も定義できるので、これを定義しだすとかなり奥が深いかもしれません。

 defformatによってCommon Lispにはないプリセットがいくつかあるので紹介してみます。

(lambda.format:format t "~\\time-interval\\" 100000)
;>>  1 day 3 hours 46 minutes 40 seconds
;=>  NIL

(lambda.format:format t "~\\datime\\") ;>> 12-Nov-14 22:04:55 ;=> NIL

(lambda.format:format t "~\\time\\" (get-universal-time)) ;>> 12-Nov-14 22:04:30 ;=> NIL

(lambda.format:format t "~\\date\\" (get-universal-time)) ;>> Wednesday the twelfth of November, 2014; 10:02:07 pm ;=> NIL

(lambda.format:print-list t "~2,'0D" '(1 2 3 4)) ;>> 01, 02, 03, 04 ;=> NIL

(lambda.format:format t "~\\scientific\\" (* 1.2 (expt 10.0 -18))) ;>> 1.20 atto ;=> NIL

(lambda.format:format t "~\\scientific\\" (* 1.2 (expt 10.0 12))) ;>> 1.20 tera ;=> NIL

(lambda.format:format t "~\\scientific\\" (* 1.2 (expt 10.0 18))) ;>> 1.20*10^18 ;=> NIL

等、主に時刻表示系の拡張が多いようです。~\scientific\は、あると便利かもしれません。

~Rの頑張り具合

 MIT系FORMATには色々な実装がありますが、~Rの表示についても色々です、Lispマシン版だと、

(format t 
        "~R"
        53999500948468952310038099781463571339047070077306365693157950031578865323656556470575617800343597469266163663053930729475284065797602507890543303778576751659589305608587610531708947533895013315667863040414361345468355481863098933946821494355280950711022187712940833316965953579893344816889296853809961893920288590813027274832826286947619604118250774207139185726707912013800939692113888334217321860037292466401359841909988633619607424903033450804126082814732707445124637525158238170968502713843469571111703598881772316107519263725449255479619898953254897560246329021003281382925172651714439764626563531074131156571665535676768755978740766873401181144256032811285441096311372679045330018428567938178094868712065068580809639275865131706462924775859365765458767892117014063125832876850531909154025650741916731357605935258947617751694792571197098071783553594056528313033244684266522404884784128003483709377045135811006490113848405279358750014669073521011548847837131136223674359357217325635332834429988293603397900589509479837107016653173146909465177952625778626216629083679921666803483394754450376941974550174091383204437716802171570061760417244518512843899701602964043259316720291129674329623222095297987389862598611240772884052719518970002304729523579043924577088361947008622544898239153515583379016768983297954121631081900889082400864067922600058400594238765010038489366358777002458031646082067722222514413839769452342632052290914556513716288379792729240853178303687025708955633480327380407923497850078420951985228579182612827147156878135218016259208631905768836986349361622245047674221778752695741210062306761661214307852665308621602864895728252916695112242072057480560958982341308100038716195984028347126604774936339276344081453742527548489965530976385763555878830918393648006130327327960704190904833212123903052891668786081836403824776774739850875709244292639039286939523104981546952953715780347934873229269429413881121903483764180865612725119566557443754191618555762081666047374746885983734614362337775745740375778738449658551507347449245742571619102174718114780803496920602156991309405209306051844513234344718816003897543309309554346752817036736575996706489202820650462415829202264497295289415112871468983090812832130317305226698442428490067416807537978549413114929045397194539604652830651471796026129295170532822597153883218867600018669161448985415894865487458662894655907371802544792611911196859473623714636886396579347590382034792368841023838815244989979251077104385311030453119874016830081042555655934964624991309367125628395577317651104735842449752179982674841263897030388895518361919869316899138079512261304365737790278071798129962236923346021371069026119976758928476946207645148585533271169588887008698168228540845488993201135317041879520393746627802502286733993852679366573642477866184470002897130179074050812975982271794837828584984428523206394416339396120267888799143183191074876414412012246672245565330593788479217065837148544042485924208932461014686265859689846010276942771331837221759)

は、

fifty-three times ten to the three thousandth power plus nine hundred ninety-nine times ten to the two thousand nine hundred ninety-seventh power plus five hundred times ten to the two thousand nine hundred ninety-fourth power plus
....
eight hundred thirty-seven million two hundred twenty-one thousand seven hundred fifty-nine

のように表示されるため、上限は特にないようです。
ちなみに、~Rで随分と頑張っているのは、CMUCLのFORMATで、上記の例だと、

fifty-three novenonagintanongentillion nine hundred ninety-nine octononagintanongentillion five hundred septenonagintanongentillion nine hundred forty-eight senonagintanongentillion four hundred sixty-eight quinquanonagintanongentillion nine hundred fifty-two quattuornonagintanongentillion three hundred ten trenonagintanongentillion thirty-eight duononagintanongentillion ninety-nine unnonagintanongentillion seven hundred eighty-one nonagintanongentillion four hundred sixty-three novemoctogintanongentillion five hundred seventy-one octooctogintanongentillion three hundred thirty-nine septemoctogintanongentillion forty-seven sexoctogintanongentillion seventy quinquaoctogintanongentillion seventy-seven quattuoroctogintanongentillion three hundred six tresoctogintanongentillion three hundred sixty-five duooctogi
....
thirty-one billion eight hundred thirty-seven million two hundred twenty-one thousand seven hundred fifty-nine

と表示されます。10の3002乗のone hundred novenonagintanongentillionまでサポートしている様子。10の3003乗からはエラーになります。

まとめ

 今回は、MIT FORMATを紹介してみました。
これが1980年前後のMITの魔拡張機能だ!と思って紹介するつもりでしたが、FORMATは割とCommon Lisp版にも機能が取り込まれていました。
Common Lispのformatの~//で便利に使える関数は、それほど流通していないようなので便利な物が流通すると良いですね。

com.informatimago.common-lisp.lisp.ibclの紹介

Posted 2014-11-24 15:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の329日目です。

com.informatimago.common-lisp.lisp.ibclとはなにか

 com.informatimago.common-lisp.lisp.ibclは、Pascal Bourguignon氏作のイメージ指向でCommon Lispを使うためのユーティリティです。

パッケージ情報

パッケージ名com.informatimago.common-lisp.lisp.ibcl
Quicklisp

インストール方法

(ql:quickload :com.informatimago.common-lisp.lisp.ibcl)

試してみる

 ibclとはImage based Common Lispの略とのことで、Common LispをSmalltalkのようにイメージ指向で使うことを支援する環境になります。
どんなユーティリティがあるかというと、exportされているのは、

  • symbols-with-sources
  • source
  • list-sources
  • list-packages-with-sources
  • *source-types*
  • save-sources

位ですが、clパッケージに対応するibclと、cl-userに対するibcl-userは定義時のソースコードを記録するようになっています。

(cl:in-package :ibcl-user)

(defpackage :latumofis (:use :ibcl))

(cl:in-package :latumofis)

(defun lomilwa (x) (montino x)) ;==> (PROGN (SETF (SOURCE 'LOMILWA ':FUNCTION) '(DEFUN LOMILWA (X) (MONTINO X))) (CL:DEFUN LOMILWA (X) (MONTINO X)))

(defun montino (x) x)

(source 'lomilwa :function) ;=> (DEFUN LOMILWA ; (X) ; (MONTINO X)) ; #<PACKAGE "MAKANITO">

(source 'montino :function) ;=> (DEFUN MONTINO ; (X) ; X) ; #<PACKAGE "MAKANITO">

(list-sources) ; (DEFINE-CONDITION IBCL::SIMPLE-PACKAGE-ERROR (PACKAGE-ERROR SIMPLE-ERROR) ; NIL) ; #<PACKAGE "COM.INFORMATIMAGO.COMMON-LISP.LISP.IMAGE-BASED-COMMON-LISP">) ; ((IBCL::NORMALIZE-PACKAGE-DESIGNATOR :FUNCTION) ; (DEFUN IBCL::NORMALIZE-PACKAGE-DESIGNATOR ; (PACKAGE) ; (LET ((IBCL::PACK (CL:FIND-PACKAGE PACKAGE))) ; (IF IBCL::PACK ; (PACKAGE-NAME IBCL::PACK) ; (ERROR 'IBCL::SIMPLE-PACKAGE-ERROR :PACKAGE PACKAGE :FORMAT-CONTROL ; ...

(list-packages-with-sources) ;=> (#<PACKAGE "LATUMOFIS"> ; #<PACKAGE "COM.INFORMATIMAGO.COMMON-LISP.LISP.IMAGE-BASED-COMMON-LISP-USER"> ; #<PACKAGE "COM.INFORMATIMAGO.COMMON-LISP.LISP.IMAGE-BASED-COMMON-LISP"> ; #<PACKAGE "COM.INFORMATIMAGO.COMMON-LISP.LISP.CL-SAVING-DEFINES"> ; #<PACKAGE "COM.INFORMATIMAGO.COMMON-LISP.LISP.SOURCE"> #<PACKAGE "SWANK-REPL"> ; ...

という感じで定義とソースを管理でき、save-sourcesでファイルに書き出せます。

(save-sources "/tmp/latumofis.lisp" :line-spacing 2)
  • latumofis.lisp
;;;; -*- mode:lisp -*-
;;;; Generated from sources saved by COM.INFORMATIMAGO.COMMON-LISP.LISP.SOURCE

(in-package "LATUMOFIS")

(defun lomilwa (x) (montino x))

(defun montino (x) x)

;;;; THE END ;;;;

 以上ように定義のソースコードはイメージの中に残るので、イメージをダンプすればソースを編集して再定義することも可能です。

編集にはソース全体をファイルに書き出しても良いでしょうし、定義をエディタに渡してどうにかすることもできそうです。

まとめ

 今回は、com.informatimago.common-lisp.lisp.ibclを紹介してみました。
Interlisp-DはSmalltalkと同じマシンで稼動し、同じようにイメージ指向でしたが、プロジェクトを書き出すmake-packageというユーティリティがあり、依存関係等を管理しつつ書き出したい定義を選択してファイルにまとめてくれるというものでした。
com.informatimago.common-lisp.lisp.ibclを使えば、make-packageのようなことが可能になりますね。

Stanford MacLISP: utilの紹介

Posted 2014-11-23 15:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の328日目です。

Stanford MacLISP: utilとはなにか

 Stanford MacLISP: utilは、Stanford大学のMacLISPのユーティリティです。恐らく作者は、Richard Gabriel氏だと思われます。

パッケージ情報

パッケージ名Stanford MacLISP: util
参考サイトUTIL.2[AID,LSP]-www.SailDart.org

インストール方法

 上記サイトからダウンロードして適当に動かします。

 Common Lispに移植してみたものがありますので良かったらどうぞ(動作確認できれば良いという程度の移植です)

試してみる

 日付は、1981-04-22なのでMacLISPにしては案外新しいようですが、上記のサイトのソースを眺めてもらうと分かるように、なんだか分からないLisp方言となっています。
異様な見た目の原因は、Richard Gabriel氏が使っていた俺構文なのですが、MacLISPにInterlisp的な構文を取り入れつつASCII以外の文字も使っていることに起因するようです。
例えばletはこう書きます。

(let x ← 42 do
  x)
;=>  42

 またパタンマッチを多用しているのも特徴でマクロもパタンマッチで書けるmatch-macroというものが沢山使われています。
match-macroの大まかな説明をすると、構文要素をパタン変数にマッチさせて、パタン変数以外をクォートするcodeという構文で包んだコードと合体させるという方式になっています。下記のifでは、

(match-macro (if) (*form1 then *form2)
  (cond ((%match '(*form2 else *form3) *form2)
         (code (cond (*form1 *form2)
                     (t *form3))))
        (t (code (cond (*form1 *form2))))))

(let *form1 ← '(pred) do (let *form2 ← '(con) do (let *form3 ← '(alt) do (CONS 'COND (CONS (APPEND *FORM1 (APPEND *FORM2 NIL)) (CONS (CONS 'T (APPEND *FORM3 NIL)) NIL)))))) ;=> (COND (PRED CON) (T ALT))

(let *form1 ← '(pred) do (let *form2 ← '(con) do (CONS 'COND (CONS (APPEND *FORM1 (APPEND *FORM2 NIL)) NIL)))) ;=> (COND (PRED CON))

マッチ具合によって展開が変わります。

 このmatch-macroで使われている%matchですが、ガードが使えるのが1980年当時としてはなかなか先進的な気がします。

(multiple-value-bind (?x *xs ?y) nil
  (%match '(?x *xs) '(1 2 3 4))
  (list ?x *xs))
;=>  (1 (2 3 4))

(multiple-value-bind (?x *xs ?y) nil (%match '(?x *xs ($r ?y evenp)) '(1 2 3 4)) (list ?x *xs ?y)) ;=> (1 (2 3) 4)

(multiple-value-bind (?x *xs ?y) nil (%match '(?x *xs ($r ?y oddp)) '(1 2 3 4)) (list ?x *xs ?y)) ;=> (NIL NIL NIL)

ということで種類ごとに適当に眺めてみます。

制御構文

 ifはthenとelseをキーワードを使います。

(if (zerop (random 2)) then 42 else 32)
;=>  42

 その他、Interlispのselectに影響を受けたselect、select=、select-matchがあります。

(select "foo"
  ("bar" "bar")
  ("foo" "foo")
  "baz")
;=>  "foo"

(select= 42 (42 "bar") (97 "foo") "else") ;=> "bar"

(let ?x ← nil do (let ?y ← nil do (let ?z ← nil do (select-match '(1 2 3) ((?x ?y ?z) (list ?x ?y ?z)) "else")))) ;=> (1 2 3)

繰り返し

 繰り返し構文も大体定番な感じですが、キーワードのdoが特徴的です。単純な繰り返しのrepeat/while/untilの他にInterlispのforに影響を受けた汎用的なforがあります。for x in xsをfor x ∈ xsと書けます。

(repeat 10 do (princ "."))
;>>  ..........
;=>  NIL
              

(until (zerop (random 3)) do (print "foo") return (print "1") (print "2") 10) ;>> ;>> "foo" ;>> "foo" ;>> "foo" ;>> "foo" ;>> "foo" ;>> "1" ;>> "2" ;=> 10 (while (zerop (random 3)) do (print 'foo))

;>> ;>> FOO ;>> FOO ;=> NIL

(let list ← '(1 2 3 4) do (for x ∈ list collect (list x))) ;=> ((1) (2) (3) (4))

(for x from 1 to 5 by 2 do (print x)) ;==> (DO ((X 1 (+ X 2))) ((< 5 X)) (PRINT X)) ;>> ;>> 1 ;>> 3 ;>> 5 ;=> NIL

(for x ∈ '(1 2 3 4) select (oddp x)) ;==> (MAPCAN (LAMBDA (X) (AND (PROGN (ODDP X)) (LIST X))) '(1 2 3 4)) ;=> (1 3)

(for x ∈ '(1 2 3 4) scan (print x)) ;>> ;>> 1 ;>> 2 ;>> 3 ;>> 4 ;=> NIL

(for x ∈ '(1 2 3 4) do (print x)) ;>> ;>> 1 ;>> 2 ;>> 3 ;>> 4 ;=> (1 2 3 4)

末尾再帰を最適化するdefun

 Clojureのloop/recurと似た感じですが、式を分析してgotoに変換します。
Clojureのrecurに相当するのは、tail-recurキーワードです。
実行していることは、Let Over Lambdaのnamed-letとほぼ同じですが、1980年に既にあったというのは面白いですね。

(tail-recursive-defun fib (n a1 a2)
  (cond ((zerop n) a2)
        ((= 1 n) a1)
        (t (tail-recur (1- n) (+ a1 a2) a1))))

(fib 100 1 0) ;=> 354224848179261915075

まとめ

 今回は、Stanford MacLISP: utilを紹介してみました。
現状はコードの断片が残っているのみで、使い方の説明も構文の使われ方の説明もないので、基本的にさっぱり分かりませんが、コードは大体復元して動かして確認してみたので上記の説明で大体合ってるんじゃないかなと思います。

local-time-durationの紹介

Posted 2014-11-22 15:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の327日目です。

local-time-durationとはなにか

 local-time-durationは、Webcheckout, Inc.作のlocal-timeと親和性の高く、timestamp形式が利用可能な期間を扱うライブラリです。

パッケージ情報

パッケージ名local-time-duration
Quicklisp
CLiKiCLiki: Article not found
Quickdocslocal-time-duration | Quickdocs

インストール方法

(ql:quickload :local-time-duration)

試してみる

 どんな関数があるかは、Quickdocsで確認できます。

 定義されている関数は下記の通りですが、大体名前から使い方が想像できます。

  • duration/=
  • duration/
  • duration-
  • duration
  • duration<=
  • duration-minimum
  • timestamp-difference
  • parse-iso8601-duration
  • duration>=
  • duration>
  • duration<
  • duration-maximum
  • duration+
  • duration-as
  • duration*
  • duration=
  • human-readable-duration
  • timestamp-duration+
  • timestamp-duration-

 2015年の1月1日から現時刻の期間を求めて、その期間分過去に戻ったtimestampを得るとするとこんな感じになります。

(let* ((now (local-time:now))
       (d (ltd:timestamp-difference (local-time:encode-timestamp 0 0 0 0 1 1 2015)
                                    now)))
  (ltd:timestamp-duration- now d))
;=>  @2016-10-14T00:00:00.000000+09:00

三週間後のタイムスタンプは、

(ltd:timestamp-duration+ (local-time:now)
                         (ltd:duration :week 3))
;=>  @2015-12-14T00:00:00.000000+09:00

等、シンプルです。

まとめ

 今回は、local-time-durationを紹介してみました。
local-timeと組み合せて手軽に期間が扱えて便利ですね。

srfi 86の紹介

Posted 2014-11-21 15:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の326日目です。

srfi 86とはなにか

 srfi 86は、Joo ChurlSoo氏による究極の束縛構文の提案です。

パッケージ情報

パッケージ名srfi 86
SRFISRFI 86: MU and NU simulating VALUES & CALL-WITH-VALUES, and their related LET-syntax

インストール方法

 SagittariusとRacketでは標準で使えます。

;;; Sagittarius
(import (srfi 86))

;;; Racket (require srfi/86)

試してみる

の記事の使い回しなのですが、コード例をSchemeで動くように書き換えるとこんな感じになります。

多値 & 分配束縛

 muが多値でnuがリストという感じです。

(alet (a (mu 1 2)
        ((b c) (mu 3 4)))
  (list a b c))
;=> ((1 2) 3 4)

(alet (((a . b) (nu '(1 2 3 4)))) (list a b)) ;=> (1 (2 3 4))

(alet (((values a b) (values 3 4))) (list a b)) ;=> (3 4)

名前付きLET

 ノーマルなnamed-letの形式に加え、束縛部のリストの終端に名前を持ってくるという斬新な手法により複数の関数を扱えるようにしてあります。さらに謎のネストも可能

(alet* tag ((a 1)
            (a b b c (mu (+ a 2) 4 5 6))
            ((d e e) b 5 (+ a b c)))
  (if (< a 10)
      (tag a 10 b c c d e d)
      (list a b c d e)))
;=> (10 6 6 5 5)

(alet fact ((n 10) (a 1))
  (if (zero? n)
      a
      (fact (- n 1) (* a n))))
;=> 3628800

;; 名前が後ろにある形式の名前付きLET

(alet (((n 10) (a 1) . fact))
      (if (zero? n)
          a
          (fact (- n 1) (* a n))))
;=> 3628800

;; intagとtagで入れ子
(alet* ((a 1)
        ((b 2)
         (b c c (mu 3 4 5))
         ((d e d (mu a b c)) . intag)
         . tag)
        (f 6))
  (if (< d 10)
      (intag d e 10)
      (if (< c 10)
          (tag b 11 c 12 a b d intag)
          (list a b c d e f))))
;=> (1 11 12 10 3 6)

継続関係

 call/ccの糖衣構文であるlet/cc的なものもサポート。

; 脱出(継続)
(alet lp ((win)
          (list '(1 2 3 4 5 6 7)))
  (cond ((= 3 (car list))
         (win (car list)))
        (else (print (car list))
              (lp win (cdr list)))))
;->
;   1
;   2
;=> 3

and-let*

 and-let*も貪欲に取り込み

;; and-let*
(alet* ((alist '((a . 1) (b . 2) (c . 3)))
        (and (a (assoc 'b alist))))
  (cdr a))
;=> 2

Common Lispのlambda-list的なものをサポート

 Common Lispでいう&rest、&optional、&keyを越えるものをサポート。キーワードのキーとして文字列も使えます。

;; キーワードで分配
(alet ((key '(b 20 a 10 c 30)
            (a :init)
            (b :init)
            (c :init)
            (d :init)))
  (list a b c d))
;=> (10 20 30 :init)

;; Common Lispのdestructuring-bindとの比較 (destructuring-bind (&key ((a a) :init) ((b b) :init) ((c c) :init) ((d d) :init)) '(b 20 a 10 c 30) (list a b c d)) ;=> (10 20 30 :INIT)

;; もっとエグい (alet ((key '(:a 10 :cc 30 40 b 20) ((a :a) 1) ((b :b) 2) ((c :cc) 3) . d)) (list a b c d)) ;=> (10 2 30 (40 b 20))

;; 文字もキーにできる (alet ((key '("a" 10 "cc" 30 40 b 20) ((a "a") 1) ((b "b") 2) ((c "cc") 3) . d)) (list a b c d)) ;=> (10 2 30 (40 B 20))

letrec系

 letrec形式も勿論サポート

(alet ((rec (fact (lambda (n)
                    (if (zero? n)
                        1
                        (* n (fact (- n 1))))))))
  (fact 10))
;=> 3628800

その他

(let ((a #f) (b #f))
  (alet ((a :a)
         (b :b)
         (() (set! a 100)
             (set! b 200)))
    (list a b)))
;=> (:a :b)
(let (a b) (set! a 100) (set! b 200)
        (alet ((a :a) (b :b))
              (list a b)))
;=> (:a :b)

(let ((a #f) (b #f))
  (alet* ((a :a)
          (b :b)
          (() (set! a 100)
              (set! b 200)))
    (list a b)))
;=> (100 200)
(let (a b)
     (alet* ((a :a) (b :b))
            (set! a 100)
            (set! b 200)
            (list a b)))
;=> (100 200)

(alet ((cat '(1 -2 3) (a 0 (positive? a)) (b 0 (positive? b)) (c 0 (positive? c)) . d)) (list a b c d)) ;=> (1 3 0 (-2))

色々複合した例

(let ((m #f) (n #f))
  (alet* ((a (begin (display "1st") 1))
          ((b c) 2 (begin (display "2nd") 3))
          (() (set! m #f) (set! n (list 8)))
          ((d (begin (display "3rd") 4))
           (key '(e 5 tmp 6) (e 0) ((f 'tmp) 55)) . p)
          g (nu (begin (display "4th") 7) n)
          ((values . h) (apply values 7 (begin (display "5th") n)))
          ((m 11) (n n) . q)
          (rec (i (lambda () (- (j) 1)))
               (j (lambda ()  10)))
          (and (k (begin (display "6th") m))
               (l (begin (display "end") (newline) 12)))
          (o))
    (if (< d 10)
        (p 40 50 60)
        (if (< m 100)
            (q 111 n)
            (begin (display (list a b c d e f g h
                                  (i)
                                  (j)
                                k l m n))
                   (newline))))
    (o (list 'o p q))
    (display "This is not displayed")))
;-> 1st2nd3rd4th5th6thend
;-> 4th5th6thend
;-> 6thend
;-> (1 2 3 40 50 60 (7 8) (7 8) 9 10 111 12 111 (8))
;=> (o #<closure #<identifier p#user>> #<closure #<identifier q#user>>)

まとめ

 今回は、srfi 86を紹介してみました。
Joo ChurlSoo氏のSRFIは面白いものが多いのですが、紹介するのもなかなか大変です。

1amの紹介

Posted 2014-11-20 15:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の325日目です。

1amとはなにか

 1amは、James M. Lawrence氏作のシンプルなfiveam風のテストフレームワークです。

パッケージ情報

パッケージ名1am
Quicklisp
CLiKiCLiki: Article not found
Quickdocs1am | Quickdocs

インストール方法

(ql:quickload :1am)

試してみる

 どんな関数があるかは、Quickdocsで確認できます。

 1amは約60行程のコードとのことですが、マルチスレッドな大きめのプロジェクトでは、良く知られたテストフレームワークでは問題が起きていたとのことで、これを解消するためシンプルが一番という哲学で作られたもののようです。
特長として説明があるのは、

  • テストの失敗時点でテストが停止する(ブレイクポイントで中断)
  • テスト順に依存したバグを排除するためテストは都度シャッフルして実行される
  • テストケースはテスト名と同名の関数になる
  • 先にコンパイルしてから実行する
  • 速い(fiveamの約8倍)

とのことです。
書式はfiveamとほぼ同じなので、手元でfiveamで書いていて遅いと感じていたものを1amに置き換えてみましたが、かなり速くなりました。

ASDFとの連携

 1amの仕組みは、1am:*tests*にテストの関数を詰め込んで実行するという素朴なものです。
fiveamのようにテストをsuiteごとに管理するのではなく、基本的には大域的にこの一つのみです。管理しようと思えばできなくもない感じではありますが。
他のプロジェクトとの競合を予防する場合、テストファイルの中に、1am:*tests*を初期化するコードを入れるか、asdf:prepare-opで初期化したりすることになるのかなと思います。
テストの呼び出しはrunのみなのでASDFでの記述はシンプルです。

(cl:in-package :asdf)

(defsystem :foo :serial t :depends-on (:1am ...) :components (...) :in-order-to ((test-op (load-op ...))) :perform (prepare-op :before (o c) (set (find-symbol* :*tests* :1am) '() )) :perform (test-op (o c) (let ((*package* (find-package ...))) (symbol-call :1am :run))))

*package*を書いているのは、テスト関数は、通常の関数なので大域変数に影響を受けることになるためです。
パッケージやリードテーブルに影響を受ける印字系のプログラムではテスト関数内で影響を受けないように書くか、このようにtest-opで保護するかになるかと思います。

まとめ

 今回は、1amを紹介してみました。
なかなかシンプルで良いかもしれません。

MIT Lisp Machine: Hierarchical Packagesの紹介

Posted 2014-11-19 15:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の324日目です。

MIT Lisp Machine: Hierarchical Packagesとはなにか

 MIT Lisp Machine: Hierarchical Packagesは、MIT Lisp Machineのパッケージシステムです。

パッケージ情報

パッケージ名MIT Lisp Machine: Hierarchical Packages
LispマシンマニュアルLisp Machine Manual 6th ed.: Packages

試してみる

 それまでのoblistでのシンボルの管理をパッケージにまとめて大規模な開発を可能にしたのがLispマシンのパッケージかと思います。
仕組みが固定し初めたのは大体1978年位でしょうか。
Common Lispのパッケージの関数と並べると下記のようになります。

  • package-declare / defpackage
  • pkg-create-package / make-package
  • pkg-name / package-name
  • symbol-package / symbol-package
  • pkg-find-package / find-package
  • kill-package / delete-package
  • pkg-goto / in-package
  • pkg-bind / (let ((*package* pkg)) ...)

階層パッケージ

 一見してCommon Lispと比較して違うところは、階層を成しているということです。
初期状態の階層は下記の通り

                           global                     keyword
                             |                          
       /-----------------------------------          fonts
       |     |          |          |       |
     user  zwei      system      format  (etc)        cli
                        |
                /----------------------------------
                |          |     |     |    |      |
         system-internals  eh  chaos  cadr  fs  compiler

 下記のように書くことで階層分けが可能です。

(package-declare aaa global 100 nil) 
(package-declare bbb aaa 100 nil)
(package-declare ccc bbb 100 nil)

こうすると、aaaの下にbbb、bbbの下にcccが作られるので、cccのシンボルdは、aaa:bbb:ccc:dということになります。
ちなみに、Common Lispのようにエクスポートしないとpkg::symと記述しなければいけないということはありません。
シンボルの継承は、上から下に勝手に継承してきます。つまり、

(intern "X" "AAA")
(eq 'aaa:x 'aaa:bbb:ccc:x)
;=> T

みたいなことになります。
これを防ぐのがCommon Lispと同じくshadowで

(shadow "X" 'aaa:bbb:ccc)

(eq 'aaa:x 'aaa:bbb:ccc:x) ;=> NIL

とできます。しかし、

(eq 'aaa:z 'aaa:bbb:ccc:z)
;=> T

でも

(eq 'aaa:bbb:ccc:q 'aaa:q)
;=> NIL

だったりして、評価順が関係してきてややこしいです。

keywordパッケージはuser

 パッケージシステムができた当初(というかCommon Lisp登場まで)はkeywordパッケージというものはなく、:fooと書けば、user:fooのことでした。
更に、自己評価オブジェクトでもなかったのでクォートを付ける必要がありました。
昔のコードで ':foo と書いてあることがあるのは、このためです。
この為、userパッケージはサブパッケージが作れない等の制限をつけていたようなのですが、Common Lispが出てくるあたりでkeywordパッケージもできたようです。

パッケージの指定は、-*- Packge: -*-で行なう

 上記の一覧では、pkg-gotoというものがありますが、基本的にパッケージの宣言は、ファイル最上部の属性リストで宣言していました。

階層パッケージの活用され具合

 Lispマシンのソースを眺める限りでは、特に階層分けを活かしたコードというのは無かったようです。
上部のパッケージから無条件でシンボルを継承してくるというのが良くなかったのか、何が悪かったのかは不明ですが、そんな為か、Common Lispをサポートする辺りになってくると、'aaa:bbb:ccc:xも'ccc:xも同じ意味になったりしていて、これだと実質パッケージ名はグローバルに唯一のものしか付けられなくなってきます。

面白い機能

 Common Lispには無い機能として、relative-names/relative-names-for-me、invisibleがあります。
relative-namesは、SBCLのlocal-nicknamesと同じで他のパッケージをパッケージローカルで別名で参照できます。
relative-names-for-meはその逆みたいですが詳細は不明です。
invisibleは、(list-all-packages)には登録されないということで、シンボルでいうuninterned symbolみたいな感じです。名前は付くもののfind-packageでは見付けられません。

まとめ

 今回は、MIT Lisp Machine: Hierarchical Packagesを紹介してみました。
不特定多数の人がバラバラに開発をしつつも統一しようとすれば、Perl/CPANのような名前の階層化が便利なのかなと思いますが、どうなのでしょう。
Common Lispの場合は、パッケージ名の衝突を回避する方法が面倒なのが厄介ですね。

Older entries (1903 remaining)