#:g1

CLRFI-1: featurepの紹介

Posted 2014-04-28 15:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の119日目です。

CLRFI-1: featurepとはなにか

 CLRFI-1: featurepは、処理系やホスト機の機能を判定する関数です。CLRFI-1として制定されていました。

パッケージ情報

パッケージ名CLRFI-1: featurep
プロジェクトサイト(消滅) CLRFI: 1: FEATUREP

インストール方法

 プロジェクトページにソースがあるので適当にコピペしましょう。
alexandriaやasdfにも同様のものが含まれているので、そっちを使っても良いでしょう。

;;; CLRFI 1: FEATUREP

(in-package "COM.ALPHAGEEKSINC.CLURFI.SYSTEM-CONSTRUCTION.FEATUREP")

;;; The package is nicknamed "COMMON-LISP.EXTENSIONS.SYSTEM-CONSTRUCTION.FEATUREP" ;;; "CL.EXT.SYSTEM-CONSTRUCTION.FEATUREP", "FEATUREP", plus the CLRFI ;;; marker "CLRFI-<nn>". ;;; Notes. The implementation reflects the requirements posed by the ;;; ANSI specification on the #- and #+ read macros. The two read ;;; macros bind *PACKAGE* to the KEYWORD package. Hence the test is ;;; done on :AND, :OR, and :NOT.

(use-package "COMMON-LISP")

(defun featurep (feature-expression) (etypecase feature-expression (symbol (not (null (member feature-expression *features*)))) (cons ;; Not LIST, as we've already eliminated NIL. (ecase (first feature-expression) (:and (every #'featurep (rest feature-expression))) (:or (some #'featurep (rest feature-expression))) (:not (not (featurep (cadr feature-expression))))))))

試してみる

 基本的に、#+や、#-と同じ機能の関数で、emacs lispのfeaturepや、MacLISPの(status feature ...)に似たものです。

(clrfi-1:featurep '(:and :sbcl (:or :x86-64 :x86)))
;=>  T

のように書けます。これくらいの複雑度だとfeaturepを使わないと、

(and (find :sbcl *features*)
     (or (find :x86-64 *features*)
         (find :x86 *features*))
     t)
;=>  T

のようにごちゃっとします。

関連

まとめ

 今回は、CLRFI-1: featurepを紹介してみました。
CLRFIは、2004年頃に立ち上がった、SchemeのSRFIをCommon Lispでやろうというプロジェクトだったようですが、現在消滅したようです。
現在、似たようなところとしては、CDRプロジェクトがあります。

deftransformの紹介

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

(LISP Library 365参加エントリ)

 LISP Library 365 の118日目です。

deftransformとはなにか

 deftransformは、CMUCL等のPythonコンパイラの処理系で利用できる最適化のための機能です。

インストール方法

 CMUCL等Pythonコンパイラ系で利用できます〈SBCL/SCL/CMUCL〉。
deftransform/defknownは、SBCLでは、SB-Cパッケージ、CMUCL/Scieneer CLでは、C〈COMPILER〉パッケージに含まれています。

試してみる

 Common Lispの最適化/高速化は基本的に実行時において無駄な部分を削る感じになります。
具体的には、実行時の型チェックを外したり、引数の数やキーワード引数/オプショナル引数の使われ方がコンパイル時に判明している場合は、実行時判定しないものに置き換えてしまったりします。

 大抵の場合には、コンパイラマクロ位で用は済むのですが、コンパイラマクロが基本的に型情報を扱うことができない所為で、いまいちだなと思うことがたまにありました。〈勿論、コンパイラマクロの展開時に定数になっているものは型が判別できます〉。

 この辺りの問題が、deftransformで解決できるようなので簡単に紹介してみます。

 こういう構成のgeneric:+というものがあるとします。


(defpackage generic 
  (:use)
  (:export :+))

(defun generic:+ (&rest args) (reduce #'+_2op args :initial-value (etypecase (car args) (NUMBER 0) (STRING (make-string 0)) (VECTOR (make-sequence 'vector 0)) (LIST (make-list 0)))))

(defun +_2op (x y) (cond ((and (numberp x) (numberp y)) (+ x y)) ((and (stringp x) (stringp y)) (concatenate 'string x y)) ((and (listp x) (listp y)) (concatenate 'list x y)) ((and (and (vectorp x) (not (stringp x))) (and (vectorp y) (not (stringp y)))) (concatenate 'vector x y)) (T (error "Type mismatch: (~S ~S)." x y))))

 文字列等、シークエンスも数字も扱えるような関数です。

(generic:+ 1 1)
;=>  2

(generic:+ '(a b c) '(d e f)) ;=> (A B C D E F)

(generic:+ "abc" "def") ;=> "abcdef"

 これはこれで良いのですが、型が決まっているような状況で遅いのが気になります。計測してみると、数値専用の関数を利用した場合と比べても10倍以上遅いようです。

(defun tak (x y z)
  (declare (optimize (debug 0) (safety 0) (speed 3))
           (fixnum x y z))
  (if (<= x y)
      z
      (tak (tak (generic:+ -1 x) y z)
           (tak (generic:+ -1 y) z x)
           (tak (generic:+ -1 z) x y))))

(tak 18 9 0) ;=> 9 #|------------------------------------------------------------| Evaluation took: 2.272 seconds of real time 2.272142 seconds of total run time (2.216139 user, 0.056003 system) [ Run times consist of 0.040 seconds GC time, and 2.233 seconds non-GC time. ] 100.00% CPU 5,439,652,227 processor cycles 379,901,584 bytes consed

Intel(R) Core(TM)2 Duo CPU P8600 @ 2.40GHz |------------------------------------------------------------|#

(defun tak-normal (x y z) (declare (optimize (debug 0) (safety 0) (speed 3)) (fixnum x y z)) (if (<= x y) z (tak-normal (tak-normal (+ -1 x) y z) (tak-normal (+ -1 y) z x) (tak-normal (+ -1 z) x y))))

(tak-normal 18 9 0) ;=> 9 #|------------------------------------------------------------| Evaluation took: 0.167 seconds of real time 0.164011 seconds of total run time (0.164011 user, 0.000000 system) 98.20% CPU 398,458,152 processor cycles 0 bytes consed

Intel(R) Core(TM)2 Duo CPU P8600 @ 2.40GHz |------------------------------------------------------------|#

 書き直せば良いという話もありますが、ここは最適化でがんばってみましょう。
まずは、可変長引数の処理をコンパイラマクロを利用して実行時からコンパイル時に移動してみます。

(define-compiler-macro generic:+ (&rest args)
  (reduce (lambda (x form)
            `(+_2op ,x ,form))
          args
          :initial-value `(etypecase ,(car args)
                            (NUMBER 0)
                            (STRING (make-string 0))
                            (VECTOR (make-sequence 'vector 0))
                            (LIST (make-list 0)))
          :from-end T))
(tak 18 9 0)
;=> 9
#|------------------------------------------------------------|
Evaluation took:
  0.485 seconds of real time
  0.484030 seconds of total run time (0.484030 user, 0.000000 system)
  99.79% CPU
  1,161,442,746 processor cycles
  0 bytes consed

Intel(R) Core(TM)2 Duo CPU P8600 @ 2.40GHz |------------------------------------------------------------|#

 大分速くなりました。コンパイラマクロを展開すると(generic:+ -1 z)は

(+_2OP -1 
       (+_2OP Z
              (ETYPECASE -1
                (NUMBER 0)
                (STRING (MAKE-STRING 0))
                (VECTOR (MAKE-SEQUENCE 'VECTOR 0))
                (LIST (MAKE-LIST 0)))))

となっているので、速くなるのは分かります。しかし、+_2opが扱うのはFIXNUMであることをDECLAREで指定しているので、+_2op内部で改めてわざわざ判定しているのが勿体無いというところです。
この隔靴掻痒感をdeftransformでどうにかしてみます。

(sb-c:defknown +_2op (t t) t)

(sb-c:deftransform +_2op ((x y) (fixnum fixnum)) `(cl:+ x y))

 deftransformは、コンパイラマクロっぽいですが、コンパイラの最適化で作用します。〈ICR(Implicit Continuation Representation)の最適化の際に変換されるようです。〉
上記の例では、2つの引数がどちらもFIXNUMの場合の式の変換方法を指定しています。

(tak 18 9 0)
;=> 9
#|------------------------------------------------------------|
Evaluation took:
  0.169 seconds of real time
  0.168010 seconds of total run time (0.168010 user, 0.000000 system)
  99.41% CPU
  404,691,399 processor cycles
  0 bytes consed

Intel(R) Core(TM)2 Duo CPU P8600 @ 2.40GHz |------------------------------------------------------------|#

 DISASSEMBLEすると+_2opが消えているのも確認できます。

; disassembly for TAK (assembled 225 bytes)
L0:     CMP R13, RAX                    ; no-arg-parsing entry point
        JLE L1
        MOV [RBP-24], RCX
        MOV [RBP-32], R13
        MOV [RBP-40], RAX
        LEA RSI, [R13-2]
        MOV RDI, RBP
        LEA RDX, [RSP-16]
        SUB RSP, 56
        MOV R13, RSI
        MOV [RDX], RDI
        MOV RBP, RDX
        CALL #x1018BDCD7B
        MOV RCX, [RBP-24]
        MOV R13, [RBP-32]
        MOV RAX, [RBP-40]
        MOV [RBP-8], RDI
        MOV RSI, RAX
        SAR RSI, 1
        ADD RSI, -1
        MOV R8, R13
        MOV RDI, RBP
        SHL RSI, 1
        LEA R9, [RCX+RCX]
        SAR R8, 1
        LEA RDX, [RSP-16]
        SUB RSP, 56
        MOV R13, RSI
        MOV RAX, R9
        MOV RCX, R8
        MOV [RDX], RDI
        MOV RBP, RDX
        CALL #x1018BDCD7B
        MOV RAX, [RBP-40]
        MOV R13, [RBP-32]
        MOV RCX, [RBP-24]
        MOV R8, RDI
        MOV [RBP-16], R8
        LEA RBX, [RCX-1]
        MOV RDI, R13
        MOV R10, RAX
        MOV RSI, RBP
        SHL RBX, 1
        SAR R10, 1
        LEA RDX, [RSP-16]
        SUB RSP, 56
        MOV R13, RBX
        MOV RAX, RDI
        MOV RCX, R10
        MOV [RDX], RSI
        MOV RBP, RDX
        CALL #x1018BDCD7B
        MOV R8, [RBP-16]
        MOV R13, [RBP-8]
        MOV RAX, R8
        MOV RCX, RDI
        SAR RCX, 1
        JMP L0
L1:     MOV R10, RCX
        SHL R10, 1
        MOV RDI, R10
        MOV RSP, RBP
        POP RBP
        RET

 ちなみに、比べてみると分かりますが、CL:+を使ったものとDISASSEMBLEの結果は同じになっているので、速度も全く同じということになります。

関連

まとめ

 今回は、deftransformを紹介してみました。
上記の例は、deftransformの初歩的なところです。もっと突っ込んだことが色々できるようですので、面白いと思った方は試してみて、記事でも書いてもらえると嬉しいです。
ちなみに、参考資料ですが、ウェブにもほとんど転がっていないようで、処理系内部での使われ方を参照する他ないようです。
deftransformは、完全に処理系依存機能ですが、他の処理系でもこういった類の最適化ができないかを調べてみたいと思います。

cl-hooksの紹介

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

(LISP Library 365参加エントリ)

 LISP Library 365 の117日目です。

cl-hooksとはなにか

 cl-hooksは、Jan Moringen氏作の関数やオブジェクトにフック機構を実現するライブラリです。

パッケージ情報

パッケージ名cl-hooks
Quicklisp
参考サイト
CLiKihttp://cliki.net/cl-hooks
Quickdocshttp://quickdocs.org/architecture.hooks

インストール方法

(ql:quickload :cl-hooks)

試してみる

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

 cl-hooksは、既存の関数の実行や、変数の呼び出しにフックを掛けるというものではなく、新しくフックの枠組みを提供するもののようです。

(defvar *my-hook* nil
  "My hook is only run for educational purposes.")

(hooks:add-to-hook '*my-hook* (lambda (x) (format t "my-hook called with argument ~S~%" x)))

(hooks:run-hook '*my-hook* 1) ;>> my-hook called with argument 1 ;>> ;=> NIL

(documentation '*my-hook* 'hooks::hook) ;=> "My hook is only run for educational purposes."

(defclass my-class () ((my-hook :initarg :my-hook :type list :initform nil :documentation "This hook bla bla")))

(defvar *my-object* (make-instance 'my-class))

(hooks:object-hook *my-object* 'my-hook) ;=> #<HOOKS:OBJECT-HOOK MY-HOOK PROGN (0) {101D5E9B43}>

(hooks:add-to-hook (hooks:object-hook *my-object* 'my-hook) (lambda (x) (format t "my-hook called with argument ~S~%" x))) ;=> #<FUNCTION (LAMBDA (X)) {101DD3271B}> ; NIL

(hooks:object-hook *my-object* 'my-hook) ;=> #<HOOKS:OBJECT-HOOK MY-HOOK PROGN (1) {101D5E9B43}>

(hooks:run-hook (hooks:object-hook *my-object* 'my-hook) 1) ;>> my-hook called with argument 1 ;>> ;=> NIL

(documentation (hooks:object-hook *my-object* 'my-hook) 'hooks::hook) ;=> "This hook bla bla"

(defparameter *external-hook* (hooks:external-hook *my-object* 'my-external-hook)) ;=> *EXTERNAL-HOOK*

(hooks:external-hook *my-object* 'my-external-hook) ;=> #<HOOKS:EXTERNAL-HOOK MY-EXTERNAL-HOOK PROGN (0) {101E580E33}> ; T

(hooks:add-to-hook *external-hook* (lambda (x) (format t "my-external-hook called with argument ~S~%" x))) ;=> #<FUNCTION (LAMBDA (X)) {101F26CDAB}> ; NIL

(hooks:run-hook *external-hook* 1) ;>> my-external-hook called with argument 1 ;=> NIL

上記のコード例のように、フックは、関数にもオブジェクトにも掛けられますが、別個に管理するとなると、予めフックを掛けることになりそうなので、既存のシステムに取り入れたりするのは難しそうです。
また、フックの呼び出し方も通常の関数呼び出しの書法と微妙に異なり、cl-hooksの流儀に従わないといけない感があります。
ちなみに、cl-hooksのフックには色々機能が盛り込まれていて、ドキュメンテーションが付けられたり、メソッドコンビネーションのようなものもあります。詳しくはドキュメントを参照してください。

まとめ

 今回は、cl-hooksを紹介してみました。
やはり、フック系の機構は既存のものに付け加えたいもののように思われますが、設計段階からある程度想定できる場合は、便利だったりするのかもしれません。

tinaaの紹介

Posted 2014-04-24 17:30:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の115日目です。

tinaaとはなにか

 tinaaは、Gary Warren King氏作のCommon Lisp製のCommon Lispのコード用のドキュメンテーションツールです。

パッケージ情報

パッケージ名tinaa
Quicklisp
CLiKihttp://cliki.net/tinaa
Quickdocshttp://quickdocs.org/tinaa
common-lisp.nethttp://common-lisp.net/tinaa

インストール方法

(ql:quickload :tinaa)

試してみる

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

 使い方はシンプルに

(tinaa:document-system :package 'ppcre "/tmp/cl-ppcre/")

のようにパッケージ名と出力先のディレクトリを指定すればOKです。
出力先のディレクトリ直下に下図のようなHTMLファイルが生成されます。

tinaa

 基本的に関数や変数のドキュメンテーションストリングを集めてきて一覧にしています。手元の環境では、document-systemを実行した際にdefsystem-compatibilityの%map-system-filesでコケました。
参考までにいい加減なパッチを下記に記載してみますので同様の現象の場合試してみてください。

(in-package #:defsystem-compatibility)

(defmethod %map-system-files ((module asdf:module) function system-closure? include-pathname? include-non-source?) (mapc (lambda (thing) ;;(format t "~% ~A" thing) (%map-system-files thing function system-closure? include-pathname? include-non-source?)) #+(:and :asdf (:not :asdf2)) (safe-slot-value module 'asdf::components) #+:asdf2 (asdf:component-children module)))

;;; --------------------------------------------------------------------------- (defmethod %map-system-files ((system asdf:system) function system-closure? include-pathname? include-non-source?) (when system-closure? (dolist (system (collect-system-dependencies system)) (%map-system-files (find-system system) function system-closure? include-pathname? include-non-source?)))

;(format t "~%~A" system) (let ((*relative-pathname* (safe-slot-value system 'asdf::relative-pathname))) (mapc (lambda (thing) ;(format t "~% ~A" thing) (%map-system-files thing function system-closure? include-pathname? include-non-source?)) #+(:and :asdf (:not :asdf2)) (safe-slot-value system 'asdf::components) #+:asdf2 (asdf:component-children system))))

まとめ

 今回は、tinaaを紹介してみました。
綺麗に一覧で参照できるようになると、関数や変数のドキュメンテーションを充実させてみたい気にもなりますね。

eggs: adviceの紹介

Posted 2014-04-23 15:30:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の114日目です。

eggs: adviceとはなにか

 eggs: adviceは、Felix L. Winkelmann氏作のChickenでADVICE機構を実現するライブラリです。

パッケージ情報

パッケージ名eggs: advice
Eggs
Eggsドキュメントadvice - The Chicken Scheme wiki

インストール方法

 UNIX系のOSでは、

$ sudo chicken-install advice

すれば、

(use advice)

で使えます。

試してみる

 eggs: adviceでも定番のbefore/after/aroundが提供されています。

(define (bamatu x)
  (format #t "~A~%" x))

(define bamatu-old bamatu)

(bamatu 42) ;=> 42

という定義がある場合、

(advise 'around bamatu
        (lambda (f args)
          (format #t "before~%")
          (f (car args))
          (format #t "after~%"))
        around:)

とすれば、

(bamatu 42)
;>> before
;>> 42
;>> after
;=> #<unspecified>

という風に使えます。
aroundはフック関数に、フックされる関数と引数リストが渡るようなインターフェイスになっています。
また、

(eq? bamatu-old bamatu)
;=> #t

のようです。

 外すには、

(unadvise bamatu around:)

のようにします。

 他、beforeとafterは、

(define (badialma x) x)

(badialma 42) ;=> 42

(advise 'before badialma (lambda (x) (format #t "before: args => ~A~%" x)) before:)

(badialma 42) ;>> before: args => (42) ;=> 42

(define (halito x) x)

(halito 42) ;=> 42

(advise 'after halito (lambda (x) (format #t "after: args => ~A~%" x) done:) after:)

(list (halito 42)) ;>> after: args => (42) ;=> 42

のような感じ。
after/around時の関数の返り値もオーソドックスな扱いかなと思います。

まとめ

 今回は、eggs: adviceを紹介してみました。
シンプルですが、大体のことはできそうです。

exportingの紹介

Posted 2014-04-22 17:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の113日目です。

exportingとはなにか

 exportingは、CLISPで有名なBruno Haible氏作の関数/変数定義のユーティリティです。

パッケージ情報

パッケージ名exporting
プロジェクトサイト CLOCC - Common Lisp Open Code Collection / Hg / [e6b24a] /src/ext

インストール方法

 CLOCCのプロジェクトサイトからダウンロードしてきて適当に導入します。
CLOCCは活動が停止しているものと思っていましたが、最近も更新があったりするようです。

$ hg clone http://hg.code.sf.net/p/clocc/hg clocc-hg

で全体のソースも取得できます。
src/ext/exporting/exporting.lisp が目的のソースです。

試してみる

 そもそも何をするものかというと、定義した関数/変数はエクスポートもしておきたい、というニーズを叶えるもので、定義+エクスポート、というだけのものです。
マクロ展開するとそのものズバリな内容になっています。

(exporting:defun foo (n) n)
==> (CL:PROGN (CL:EXPORT 'FOO) (CL:DEFUN FOO (N) N))
  • DEFCLASS
  • DEFCONSTANT
  • DEFGENERIC
  • DEFINE-COMPILER-MACRO
  • DEFINE-CONDITION
  • DEFINE-METHOD-COMBINATION
  • DEFINE-MODIFY-MACRO
  • DEFINE-SETF-EXPANDER
  • DEFINE-SYMBOL-MACRO
  • DEFMACRO
  • DEFMETHOD
  • DEFPARAMETER
  • DEFSETF
  • DEFSTRUCT
  • DEFTYPE
  • DEFUN
  • DEFVAR

がexportingでサポートされているフォームです。

まとめ

 今回は、exportingを紹介してみました。
SBCL等では、CL:DEFPACKAGE内でエクスポートされるシンボルも定義した場合、別個に分散してCL:EXPORTで宣言したものがあると、エラーにされたりするので、CL:DEFPACKAGE内の:EXPORTは使わない等の工夫が必要になるかと思います。〈SBCLでは、CL:DEFPACKAGEの“If the new definition is at variance with the current state of that package, the consequences are undefined.”を厳しく解釈している様子〉

 exportingに似た機能を持つものは他にも幾つかありますが、個人的には、開発時には便利なものの、パッケージ/シンボルの設計は一度決まれば、そんなに変更されるものでもないと思うので、CL:DEFPACKAGEにまとめても大した手間でもないだろう、という感じです〈SLIMEだとパッケージ定義にも飛んで行けますし…〉

chtml-matcherの紹介

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

(LISP Library 365参加エントリ)

 LISP Library 365 の112日目です。

chtml-matcherとはなにか

 chtml-matcherは、Ian Eslick氏作のHTML(LHTML)に対してのパタンマッチのライブラリです。

パッケージ情報

パッケージ名chtml-matcher
Quicklisp
プロジェクトサイトeslick/chtml-matcher · GitHub
Quickdocshttp://quickdocs.org/chtml-matcher

インストール方法

(ql:quickload :chtml-matcher)

でインストールできますが、

; in: DEFUN SET-BINDING
;     (STDUTILS:ASSOC-SETF (CHTML-MATCHER::BINDINGS CHTML-MATCHER::DICT) (CHTML-MATCHER::CLEAN-VAR CHTML-MATCHER::VAR) CHTML-MATCHER::VALUE)
; --> LET* ASSOC 
; ==>
;   #<FUNCTION EQ>
; 
; caught ERROR:
;   Objects of type FUNCTION can't be dumped into fasl files.

のようなエラーになる場合、

(defmacro-exported assoc-setf (place key value &optional (test 'eq)) ...

のようにtest #'eqを 'eqに直しましょう。〈マクロ展開時に評価してしまうため、関数オブジェクトをfaslに書き出すことになってしまうのが原因〉

試してみる

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

 非常に大まかな説明のみで具体的な利用方法の解説がないのですが、解説によれば、"<"から始まるシンボルは、XPATHでいう//に相当するとのこと。
さらに、

  • all:複数回マッチ
  • merge:?
  • nth:位置指定〈何故か1オリジン〉
  • regex:正規表現でマッチ
  • fn:指定した関数を実行

のマッチのさせ方があります

 とりあえず、説明なしに簡単に使えそうなものから順に紹介します。

(defvar *html*
  (chtml-matcher:html->lhtml "<html><head></head><body><ul>
<li>a</li>
<li>b</li>
<li>c</li>
<li>d</li>
<li>e</li>
</ul></body></html>"))

find-in-lhtml

(chtml-matcher:find-in-lhtml *html* :ul nil)
;=>  (:UL NIL (:LI NIL "a") (:LI NIL "b") (:LI NIL "c") (:LI NIL "d") (:LI NIL "e"))

指定したノード以下を拾ってきます。

match-templateとget-bindings

(chtml-matcher:get-bindings 
 (chtml-matcher:match-template '(<ul () 
                                 (all ?elts
                                  (li () ?x)))
                               *html*))
;=>  ((:ELTS #<DICT 1 X> #<DICT 1 X> #<DICT 1 X> #<DICT 1 X> #<DICT 1 X>))

 match-templateでテンプレートとマッチさせ、get-bindingsでマッチしたものを取り出せます。
この例では、allでul以下の要素をまとめてeltsにマッチさせ、liの要素はxにマッチしています。

with-bindings

(chtml-matcher:with-bindings (elts)
                             (chtml-matcher:match-template '(<ul () 
                                                             (all ?elts
                                                              (li () ?x)))
                                                           *html*)
  (mapcan #'chtml-matcher:get-bindings elts))
;=>  ((:X . "a") (:X . "b") (:X . "c") (:X . "d") (:X . "e"))

 with-slots的にパタン変数と同名の束縛を作ります。

(chtml-matcher:with-bindings (a b c d)
                             (chtml-matcher:match-template 
                              '(<ul () 
                                (li () ?a)
                                (li () ?b)
                                (li () ?c)
                                (li () ?d))
                              *html*)
  (list a b c d))
;=>  ("a" "b" "c" "d")

まとめ

 今回は、chtml-matcherを紹介してみました。
あまりにもドキュメントがないので使い方は上記の説明で正しいのかさっぱりです。
説明がなくてもテストケースがあれば、なんとなく使い方も想像が付いたりもするのですが、テストケースもなく…。

Allegro CL: Fwrapの紹介

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

(LISP Library 365参加エントリ)

 LISP Library 365 の111日目です。

Allegro CL: Fwrapとはなにか

 Allegro CL: Fwrapは、Allegro CLが提供する進化版のADVICE機構です。

パッケージ情報

パッケージ名Allegro CL: Fwrap
ドキュメントサイトFwrapping and Advice

インストール方法

 標準の状態でAllegro CLのEXCLパッケージに含まれています。

試してみる

 ADVICE機構は、恐らく最近ではAOP的な機能として考えられることが多いのではないでしょうか。
Lisp処理系には60年代後半のBBN-LISPあたりからADVICE機構が含まれていましたが、Common Lispには取り入れられなかっためか、それ程馴染みがない機能となってしまった感があります。
現在メジャーなLisp方言でサポートしているものといえば、Emacs Lispがありますが、構文的には、どうもLispマシンのADVICEと似たもののようです。
Common Lispの処理系にも、大抵Lispマシンと同様のADVICE的な機能が用意されていますが、CLOSのメソッド・コンビネーションで似たようなこともできるので、そちらを利用することが多いのかもしれません〈とはいえ微妙に使いたい場面が違いますが〉

 Allegro CL: Fwrapは、そんなADVICE機構の従来の問題点を改善したものとのこと。
使い方は単純で、

(defun bamatu (x)
  (format t "~A~%" x))

(defvar *bamatu-old* #'bamatu)

(funcall *bamatu-old* 42) ;>> 42 ;>> ;=> NIL

のような関数を定義していたとすると、

(def-fwrapper bamatu-wrapper (x)
  (format t "before~%")
  (call-next-fwrapper)
  (format t "after~%"))

のようにラッパーを定義できます。
メソッド・コンビネーションの:aroundに似た感じで、call-next-fwrapperを呼ばないと元の関数は呼び出されません。

(fwrap 'bamatu 'w1 'bamatu-wrapper)
;=>  #<Function BAMATU>

(describe #'bamatu) ;>> #<Function BAMATU> is a TENURED COMPILED-FUNCTION. ;>> The arguments are (X) ;>> It has the following indicator/fwrapper pairs, from outer to inner: ;>> W1 #<Function BAMATU-WRAPPER> ;>> ;=> <no values>

(eq *bamatu-old* #'bamatu) ;=> T (bamatu 42) ;>> before ;>> 42 ;>> after ;>> ;=> NIL

(funcall *bamatu-old* 42) ;>> before ;>> 42 ;>> after ;>> ;=> NIL

(funwrap 'bamatu 'w1) ;=> #<Function BAMATU>

(bamatu 42) ;>> 42 ;>> ;=> NIL

(funcall *bamatu-old* 42) ;>> 42 ;>> ;=> NIL

定義したラッパーは、fwrapで活性化させます。明示的に活性化しないと特に何も起きません。
Emacs Lispのdefadviceも定義と活性化の二段階に分けることができるので似ているかもしれません。
ラッパーを付けた前後で関数がeqなことが改善点の一つとのこと。

マクロにラップ

 また、マクロにもラップ可能です。

(defmacro begin (&body body)
  `(progn . ,body))

(def-fwrapper begin-wrapper (&rest args) (pprint args) (call-next-fwrapper))

(fwrap 'begin :pp 'begin-wrapper)

(begin 1 2 3) ;>> ;>> ((BEGIN 1 2 3) NIL) ;=> 3

(macrolet ((foo (n) n)) (begin (foo 8))) ;>> ;>> ((BEGIN (FOO 8)) #<Augmentable INTERPRETER environment 1>) ;=> 8

マクロの環境にもアクセスできるようになっています。

ローカル関数にもラップ

 Allegro CL: Fwrapで凄いのが、ローカル関数もラップできるところ

(defun calfo (n)
  (flet ((masopic (n)
           (* 100 n)))
    (masopic n)))

(def-fwrapper masopic-wrapper (n) (let ((ans (call-next-fwrapper))) (format t "(masopic ~A) => ~A" n ans) ans))

(fwrap '(flet calfo masopic) :w1 'masopic-wrapper)

(calfo 42) ;>> (masopic 42) => 4200 ;=> 4200

(defun calfo (n) (flet ((masopic (n) (* 1000 n))) (masopic n)))

(calfo 42) ;>> (masopic 42) => 42000 ;=> 42000

再定義した場合でもラッパーは残るので、変更は別途fwrap等で行うことになります。

関連

まとめ

 今回は、Allegro CL: Fwrapを紹介してみました。
久し振りに1972年のBBN-LISPのマニュアルのADVICEの説明を読んでみましたが、典型的なADVICEの利用場面として、TRACE、BREAK、BREAKDOWN〈個別の関数の時間の測定〉が挙げられていました。
TRACEは、現在のCommon Lisp処理系でも内部でADVICE機構を利用していることが多いかと思いますが、ラッパーでBREAKやTIMEを仕込むのも使い方によってはデバッグ時に便利かもしれないですね。

3bmdの紹介

Posted 2014-04-20 10:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の110日目です。

3bmdとはなにか

 3bmdは、3b氏作のCommon Lispでmarkdown形式を処理するためのライブラリです。

パッケージ情報

パッケージ名3bmd
Quicklisp
CLiKihttp://cliki.net/3bmd
Quickdocshttp://quickdocs.org/3bmd

インストール方法

(ql:quickload :3bmd)

試してみる

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

 前回は、cl-markdownを紹介してみましたが、3bmdも大体同じようなAPIになっています。
parse-string-and-print-to-streamでmarkdownの文字列を変換してストリームに出力、3bmd:parse-and-print-to-streamでファイルを変換してストリームに出力します。

(3bmd:parse-string-and-print-to-stream 
 "## 挨拶
おはよう日本"
 *standard-output*)
;>>  
;>>  
;>>  <h2>挨拶</h2>
;>>  
;>>  <p>おはよう日本</p>
;>>  
;=>  NIL

(3bmd:parse-and-print-to-stream "3bmd-20131213-git/README.md"
                                *standard-output*)
;>> <p>Common Lisp <a href="http://daringfireball.net/projects/markdown/" >Markdown</a> -&gt; html converter, using <a href="https://github.com/nikodemus/esrap" >esrap</a> for parsing, and grammar based on <a href="https://github.com/jgm/peg-markdown" >peg-markdown</a>.</p>
;>> ....

 Lisp星人的に嬉しい機能としてcolorizeとの連携があります。

(ql:quickload :3bmd-ext-code-blocks)

として拡張機能をロードすれば、3bmd-code-blocks:*code-blocks* => Tとすることでcolorizeで処理が可能です。

(let ((3bmd-code-blocks:*code-blocks* t)
      (3bmd:*smart-quotes* t))
  (3bmd:parse-string-and-print-to-stream 
  "
```lisp
 (+ 3 3)
;=> 6
```
"
  *standard-output*))
;>>  
;>>  
;>>  <pre><code><span class="code"> <span class="paren1">(<span class="code"><a href="http://www.lispworks.com/reference/HyperSpec/Body/a_pl.htm" class="symbol">+</a> 3 3</span>)</span>
;>>  <span class="comment">;=&gt; 6</span></span></code></pre>
;>>  
;=>  NIL

 ちなみに、実際の利用では、CSSの指定をすることになるかと思います

(let ((3bmd-code-blocks:*code-blocks* t))
  (who:with-html-output (out out :prologue T :indent T)
    (:style #.colorize:*coloring-css*) 
    (3bmd:parse-and-print-to-stream 
     "/var/tmp/foo.md"
     *standard-output*)))

等々

関連

まとめ

 今回は、3bmdを紹介してみました。
colorizeと連携するのが簡単なようなので、S式で書いているこのブログもmarkdown形式に移行してみたいところです。
しかし、3bmdという名前ではmarkdownのライブラリであることに気付くのは難しいですね〈quicksearchでは見付けられますが…〉。

cl-markdownの紹介

Posted 2014-04-19 05:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の109日目です。

cl-markdownとはなにか

 cl-markdownは、Gary W. King氏作のCommon Lispでmarkdown形式を扱うためのライブラリです。

パッケージ情報

パッケージ名cl-markdown
Quicklisp
参考サイト
CLiKihttp://cliki.net/cl-markdown
Quickdocshttp://quickdocs.org/cl-markdown

インストール方法

(ql:quickload :cl-markdown)

試してみる

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

 使い方はシンプルで、markdown:markdownに変換したい文字列/ストリーム/パスオブジェクトを渡すと指定したストリームにHTMLの文字列が出力されます。

(markdown:markdown "
# cl-markdown の使い方

markdownで書いてhtmlの出力を期待している" :stream t) ;>> <h1>cl-markdown の使い方</h1><p>markdownで書いてhtmlの出力を期待している </p> ;=> #<MARKDOWN::DOCUMENT # cl-markdown の使い方 markdownで書いてhtmlの出力を期待している {101FD616C3}> ; NIL

 複数のファイル/文字列を変換するmarkdown:markdown-manyというものも用意されています。
どうも:formatに明示的に形式を渡さないとエラーになるようです〈バグ?〉。

;>>  Parsing: "こんにちは"
;>>  Rendering: "/tmp/k.html"
;=>  #<MARKDOWN::MULTI-DOCUMENT 1 children {101C709603}>
;    ((#<MARKDOWN::CHILD-DOCUMENT こんにちは {101C70A0F3}> "/tmp/k.html"))

 また、markdown:defextension、markdown:defsimple-extensionで記法を拡張することも可能です。詳細はドキュメントとサンプルコードを参照してみましょう。
ちょっと試してみた感想ですが、拡張したいことがシンプルなら、そこそこ簡単に拡張できるのではないでしょうか。

まとめ

 今回は、cl-markdownを紹介してみました。
世間で使われているmarkdownは割合に拡張されていたりするので、素のmarkdownだと寂しく感じることがありますね。

Older entries (1689 remaining)