Hatena::Groupcadr

わだばLisperになる このページをアンテナに追加 RSSフィード

2004 | 12 |
2005 | 01 | 02 | 07 | 10 | 11 |
2006 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 11 |

2011-11-09LISPブログ このエントリーを含むブックマーク

こちらのブログに加えて新しいLISPブログ始めました!

このブログと同じくLISPのことしか書かないブログになる予定です。

2011-07-17

DO&

| 16:16 | DO& - わだばLisperになる を含むブックマーク はてなブックマーク - DO& - わだばLisperになる

いつものごとく古いLispマシンのメーリングリストを漁っていたところDO&というものの提案メールを発見しました。

Date: Wednesday, 21 April 1982, 20:15-EST
From: levitt at MIT-AI, zvona at MIT-AI
Sender: Zvona at MIT-AI
Subject: LMLIB;DO&
To: info-lispm at MIT-AI

DO& is an interation macro with many of the features of LOOP and a
LISPy syntax similar to DO*.  User-definable keywords allow common
iterative constructs -- like CDRing down a list, CONSing a new list,
or counting -- to be implemented more simply and readably.  Automatic
generation of end tests makes the DO/DO* end-test form unnecessary.

The programs below, equivalent to common LISP functions, give a feel for
DO& style:

(defun length (list)
  (do& ((i &count0)
	(l &pop list &return i))))

(defun reverse (list)
  (do& ((elt &pop list &return accum)
	(accum &push elt))))

(defun listarray (array)
  (do& ((elt &aref array
	     &return (nreverse list))
	(list &push elt))))

(defun remq (item list)
  (do& ((a &pop list &return (nreverse out))
	(out &push a
	     &only-if (neq a item)))))

DO& allows most relevant information to appear within the
variable specifications, improving program clarity, and doing
away with the need for formulaic and complex bodies.

DO& has evolved over more than two years and reimplemented many times.
The rough corners have been worn off, and we now believe the current
implementation is clean enough for general release.

DO& is documented in detail in LMLIB;DO&DOC.  Bugs to BUG-DO&@AI;
users may wish to add themselves to INFO-DO&.

Here is a partial comparison of features of DO& and LOOP.  It is
perhaps not perfectly impartial.  Also, many of the features of LOOP
which DO& lacks could be added if there were demand for them.

DO& syntax is a natural extension of DO*.  LOOP is a non-LISPy
sublanguage.  Partly as a consequence, it is easier to determine
the scope of DO& keywords than that of LOOP keywords.  Since
keywords all begin with ``&'' it is easy to separate them from
non-keywords.  The simplicity of DO& syntax makes it trivial to
learn and often immediately understandably by non-users.  Also it
indents better than LOOP in Zmacs.

LOOP and DO& have roughly comparable numbers predefined iteration
keywords.  The sets are not identical; LOOP has a package mapping
keyword, and DO& has one for mapping over plists.  In any case, both
make it relatively easy to define your own keywords, so exactly which
keywords are defined by default is not very important.

LOOP supports parallel binding; DO& does not.  Of course you can
always use an extra variable to get the same effect.

DO& guarantees that variables have sensible values at all times; LOOP
does not.  In particular, in a LOOP epilogue, the values of iteration
variables may be undefined, whereas in a DO& &RETURN form, they are
always defined and take on the obvious value.

Both LOOP and DO& achieve package independence by using pname equality
in looking for keywords.

LOOP supports destructuring; DO& does not.  DO& generates automatic
dummy variables when none is supplied in a variable specification;
LOOP does not.

LOOP does more code optimization than DO& does.

DO& has many other features that are described in detail in DO&DOC.

LOOPマクロに対する対抗馬としては、最初期のものではないかと思いますが、LOOPで良く言われている問題点が既に述べられています。

面白そうなのでDO&のソースがどこかに落ちてないか探してみたのですが、残念ながらみつけることはできませんでした。

ということで、適当にサンプルコードが動く程度のものを作ってみました。

適当に考えながら作成していましたが、途中で面倒になったので、fare-matcherでやっつけることに。

しかし、サンプルコードの範囲だけではどういう動作なのかはちょっと分からないなという感じです…。

2011-07-12

CLでSRFI-30

| 19:02 | CLでSRFI-30 - わだばLisperになる を含むブックマーク はてなブックマーク - CLでSRFI-30 - わだばLisperになる

CLでSRFIな日々ですが、今回は、SRFI-30 ネスト可能な複数行コメントです。

ずばりのCLの、#| #| ...|# |# と同じなので、CLはSRFI-30対応済なのです。

とりあえず、レポジトリを作成してからソースを読んだりしているため、意味のないリポジトリを作成してしまいました。まあ良いかなと。

2011-07-09

LET*の星はRMSが考えた?

| 19:51 | LET*の星はRMSが考えた? - わだばLisperになる を含むブックマーク はてなブックマーク - LET*の星はRMSが考えた? - わだばLisperになる

古いLispMのメーリングリスト(info-lispm@mit-ai)を眺めていて発見した、新機能の告知メッセージ

Date: 15 JUN 1980 0023-EDT
From: RMS at MIT-AI (Richard M. Stallman)
Subject: Incompatible change to PROG
To: INFO-LISPM at MIT-AI

Soon PROG will bind all the variables in parallel
instead of sequentially.  This only makes a difference
if you have initializations which depend on other
variables bound by the same PROG.

If you want to bind variables sequentially, use PROG*,
which is presently the same as PROG and will not change.

Old QFASL files will continue to work as before.
The change takes effect only when the program is recompiled.

I plan to create LET* and DO*, which will both do sequential
binding like PROG*, at some time in the future.

これだけでは、PROGの変数が直列に束縛されるものをPROG*にしたのがRMSかどうかははっきりしませんが、LET*はRMSの創案かもしれないですね。

おおー、RMSかー、などと思ってしまいました。こんなことに関心がある人も少ないかと思いますが。

2011-07-03

CLでSRFI-71

| 23:09 | CLでSRFI-71 - わだばLisperになる を含むブックマーク はてなブックマーク - CLでSRFI-71 - わだばLisperになる

昨日のSmiley Hackathon #10でちょっと手をつけて挫折していたSRFI-71ですがパターンマッチが上手く行かない理由が分かって動くようになりました。

その原因ですが、Schemeの場合は変数としてtが使われていることは普通なのですが、あまり注意していなかったのでCL:Tが紛れ込んでいて、パターンマッチが意図しない方向にマッチしていたというオチでした (CL:Tは定数なので変数とは違ったマッチになる) 。

さてそのSRFI-71ですが、多値を受けるLET構文の拡張です。

自分が移植したSRFIでも多値があつかえる束縛構文はこれで4つ目な気がするんですが、SRFIでは思いの外同じ分野の提案が繰り返し提案される傾向があるように思います。

また、SRFI-71では、unconsや、unlistも一緒に定義されていたりするのですが、ネーミングがなかなか良いなと思いました。

SRFI-11のlet-valuesと違うところといえば、値を受けるところにも (values v ...)という表記を使うことと、values省略して値を並べて書くこともできる、というところでしょうか。

(in-package :srfi-71-internal)

(let (((values x1 x2 . x2+) (values 1 2 3)))
  (cons x1 (cons x2 x2+)))
;=> (1 2 3)

(let ((car cdr (uncons '(1 2))))
  (cons car cdr))
;=> (1 2)

(let (((values car cdr) (uncons '(1 2))))
  (cons car cdr))
;=> (1 2)

(let (((values . args) (unlist '(1 2 3 4 5))))
  args)
;=> (1 2 3 4 5)

ちなみに我等がSRFI-86でも同じような構文がサポートされています

(alet (((values . args) (values-list '(1 2 3 4 5))))
  args)
;=> (1 2 3 4 5)

(alet (((values x1 x2 . x2+) (values 1 2 3)))
  (cons x1 (cons x2 x2+)))
;=> (1 2 3)

Smiley Hackathon #10に参加してきました!

| 02:32 | Smiley Hackathon #10に参加してきました! - わだばLisperになる を含むブックマーク はてなブックマーク - Smiley Hackathon #10に参加してきました! - わだばLisperになる

7/2(土)にSmiley Hackathon #10またまた参加させて頂きました!

今回も主催の d:id:acotie さん #10会場提供のGaiaXさんありがとうございました。

自分は、#4、#5、#6、#7、#9と参加しているので、今回で6回目。

やってたこと

SRFIの文字列ライブラリ srfi-13をこのハッカソンで仕上げようと思っていましたが、思いの外手強いのと実装にあたりchar-setというものが定義されているのが前提なところがあり中途半端な感じに。

時間があまったので、別のSRFIで手頃なものはないかと探してみたところ、no titleがあったので、これをCLに移植することに挑戦。

こっちは比較的楽勝なものを選んだ筈でしたが、マクロの展開が上手くいかず展開のデバッグにも行き詰まり頓挫。define-syntaxのデバッグの勘が全然浅いためなかなか展開の遷移が追い掛けられません。何か簡単に追い掛けられる方法を模索したいところ。

交流

前回に引き続きArc@mgikenさんと@inuzini_jiroさんを交えつつArcネタで盛り上がりました。

Arc素晴らしいですね!!

是非また参加したいです!

2011-06-29

逆もまたしかり

| 21:35 | 逆もまたしかり - わだばLisperになる を含むブックマーク はてなブックマーク - 逆もまたしかり - わだばLisperになる

クヌース先生の「goto文を用いた構造的プログラミング」を読んでいて末尾呼び出しはgotoに直せ、また逆も正しい、との記述を読んで、そういえばあまり逆方向のことは考えないなと思ったので試してみました。

単に試してみただけで特にオチはありません…。

試してみる

(loop :for i :from 0 :below 100 :count i)

マクロの展開系を関数にしたようなgogo

(defun gogo ()
  (declare (optimize (safety 0) (speed 3)))
  (block nil
    (let ((i 0))
      (declare (type (and real number) i))
      (let ((loop-sum 0))
        (declare (type fixnum loop-sum))
        (tagbody
         NEXT-LOOP
           (when (>= i '100)
             (go END-LOOP))
           (when i
             (setq loop-sum (1+ loop-sum)))
           (setq i (1+ i))
           (go NEXT-LOOP)
         END-LOOP
           (return-from nil loop-sum))))))

(gogo)
;=> 100

disassembleしてみる

; disassembly for GOGO
;        XOR ECX, ECX ; no-arg-parsing entry point
;        XOR EDX, EDX
;        JMP L1
; L0:    ADD RDX, 8
;        ADD RCX, 8
; L1:    CMP RCX, 800
;        JL L0
;        MOV RSP, RBP
;        CLC
;        POP RBP
;        RET

上のgogo(loopマクロの展開形)をそのまま再帰呼び出しに変換したようなrecrec

(defun recrec ()
  (declare (optimize (safety 0) (speed 3)))
  (block nil
    (let ((i 0))
      (declare (type (and real number) i))
      (let ((loop-sum 0))
        (declare (type fixnum loop-sum))
        (labels ((NEXT-LOOP ()
                   (when (>= i '100)
                     (END-LOOP))
                   (when i
                     (setq loop-sum (1+ loop-sum)))
                   (setq i (1+ i))
                   (NEXT-LOOP))
                 (END-LOOP ()
                   (return-from nil loop-sum)))
          (NEXT-LOOP))))))

(recrec)
;=> 100

disassembleしてみる

; disassembly for RECREC
;       XOR ECX, ECX ; no-arg-parsing entry point
;       XOR EDX, EDX
;       JMP L2
; L0:   CMP RCX, 800
;       JL L1
;       MOV RSP, RBP
;       CLC
;       POP RBP
;       RET
; L1:   ADD RDX, 8
;       ADD RCX, 8
; L2:   JMP L0

ジャンプの順番と回数が多少違いますが、gogoもrecrecも大体同じものになりました。(まあ、最適化してるからなんですけど…)

めでたしめでたし。

2011-06-23

CLでSRFI-17

| 21:42 | CLでSRFI-17 - わだばLisperになる を含むブックマーク はてなブックマーク - CLでSRFI-17 - わだばLisperになる

悶々とSRFI実装をしていますが、今回は、SRFI-17 一般化されたset! です。

Schemeのset!は、CLでいうsetqで、setfではないのですが、setf的に読み出してきた場所に書き込むという一般化された代入を実現するのがSRFI-17です。

動作

(use-package :srfi-17)

(defun (setter car) (list val)
  (rplaca list val))

(let ((list (list 1 2 3 4)))
  (set! (car list) :foo)
  list)
;=> (:FOO 2 3 4)

(let ((list (list 1 2 3 4)))
  (funcall #'(setter car) list :foo)
  list)
;=> (:FOO 2 3 4)

移植について

set!の方は単純にsetfへのエイリアスとすればOKですが、setterはどうしたもんかと悩みます。

(setter foo)という形式は諦めれば良いのですが、処理系依存の(SBCL)の定義を追加して対処してみることにしました。(define-function-name-syntaxで定義可能)

といっても、((setter car) list :car)まで動くようにするのは大変なので(funcall #'(setter car) list :car)程度で妥協。

2011-06-20

pkg-bind再び

| 18:28 | pkg-bind再び - わだばLisperになる を含むブックマーク はてなブックマーク - pkg-bind再び - わだばLisperになる

以前Zetalispのpkg-bindをCLで再現するのに挑戦したことがありました。

pkg-bindとはどういうものかというと、囲んだ範囲は指定したパッケージ内にin-packageしたような感じに書けるというものです。

(pkg-bind :drakma
  (let ((fun #'http-request))
    (funcall fun "http://example.com")))

これが、

(LET ((DRAKMA::FUN #'DRAKMA:HTTP-REQUEST))
  (FUNCALL DRAKMA::FUN "http://example.com"))

こんな感じに解釈されます。

前回は中途半端な感じでしたが、通勤途中に前回のアプローチを一捻りする方法を思い付いたのでメモ。

前回は、パッケージ名を含んだ文字列を作成して、それを元にS式を組み立てましたが、今回は、文字列の作成に、PRINT-OBJECTを使ってみます。

具体的には、シンボルを読んで、あるオブジェクトに変換して、そのプリティプリントが、#.(CL:INTERN "FOO" "CL")という風になるようにします。

あとは、ボディを再帰的に走査して、文字列として出力し、READ-FROM-STRINGし、それをDEFMACROのボディとします。

(defclass intern-form ()
  ((name :initarg :name)
   (package :initarg :package)))

(defmethod print-object ((obj intern-form) stream)
  (format stream
          "#.(CL:INTERN ~S ~S)"
          (slot-value obj 'name)
          (slot-value obj 'package)))

(defun up-symbol (elt pkg)
  (typecase elt
    (symbol
       (let ((name (string elt)))
         (make-instance 'intern-form
                  :name name
               :package (package-name
                         (let ((elt-pkg (symbol-package elt)))
                           (cond ((eq elt-pkg (find-package pkg))
                                  pkg)
                                 ;;
                                 ((and (eq elt-pkg (find-package *package*))
                                       (find-symbol (string elt) pkg))
                                  pkg)
                                 ;;
                                 ('T elt-pkg)))))))
    ;;
    (otherwise elt)))

(defun symbol-to-intern-form (tree pkg)
  (cond ((null tree)
         tree)
        ;;
        ((atom (car tree))
         (let ((elt (car tree)))
           (cons (if (eq 'pkg-bind elt)
                     'pkg-bind
                     (up-symbol elt pkg))
                 (symbol-to-intern-form (cdr tree) pkg))))
        ;;
        ('T (cons (symbol-to-intern-form (car tree) pkg)
                  (symbol-to-intern-form (cdr tree) pkg)))))

(defmacro pkg-bind (pkg &body body)
  `(progn
     ,@(read-from-string
        (write-to-string
         (symbol-to-intern-form body (package-name pkg))))))

pkg-bindはそれほど使う機会もありませんが、他のパッケージからコピペしたコードをとりあえず手元のパッケージ内で動作確認したい場合などにそれなりに便利に使えます。

2011-06-19

CLでSRFI-9

| 16:32 | CLでSRFI-9 - わだばLisperになる を含むブックマーク はてなブックマーク - CLでSRFI-9 - わだばLisperになる

適当にSRFI実装をしていますが、今回は、SRFI-9 レコード型の定義 です。

CLでいう構造体かクラスですが、SRFI-9の define-record-typeも定義時にアクセサやモディファイア等を一式作成するものです。

動作

define-record-type の使い方ですが、定義は、

(define-record-type レコード名 コンストラクタ 述語 *(スロット アクセサ名 モディファイア名))

という風に記述します。

;; pairの定義
(define-record-type pair
  (kons x y)
  pair?
  (x kar set-kar!)
  (y kdr set-kdr!))

;; レコードの作成
(defvar kons (kons 1 2))

(pair? kons)
;=> T

(kar kons)
;=> 1

(kdr kons)
;=> 2

(set-kar! kons 100)
;=> 100

kons
;=> #S(PAIR :KAR 100 :KDR 2)

(set-kdr! kons 200)
;=> 200

kons
;=> #S(PAIR :KAR 100 :KDR 200)

上記のpairの定義は、下記のように展開されることにしてみました。

(progn
  (defstruct (pair
               (:constructor kons (x y &aux (kar x) (kdr y)))
               (:predicate pair?)
               (:conc-name ""))
    kar kdr)
  (srfi-9-internal::define-modifier kar set-kar!)
  (srfi-9-internal::define-modifier kdr set-kdr!))

仮引数とスロット名が対応していませんが、アクセサをdefstruct標準の機能で作成しているのでslot名=アクセサ名となっています。

コンストラクタの引数はdefstructにそのまま渡しているので、defstructのラムダリストが使えます。(これはboa lambda listと呼ばれるようです)

(define-record-type foo
  (mkfoo a b &key c)
  foo?
  (a fooa)
  (b foob)
  (c fooc))

(defvar foo (mkfoo 1 2 :c 3))
;=> FOO

foo
;=> #S(FOO :FOOA 1 :FOOB 2 :FOOC 3)

(incf (fooc foo) 100)
;=> 103

foo
;=> #S(FOO :FOOA 1 :FOOB 2 :FOOC 103)

defstructはオプションが豊富で魔窟な感じですが、こういうシンプルに使えるマクロを用意して使ってみるのもありかなと思います。

2011-06-17

CLでSRFI-4

| 22:31 | CLでSRFI-4 - わだばLisperになる を含むブックマーク はてなブックマーク - CLでSRFI-4 - わだばLisperになる

必死に願掛けSRFI実装をしていますが、今回は、SRFI-4 一様数値ベクタ型 です。

通常のベクターは要素の型として任意のオブジェクトが取れるのですが、同じ型の数値しか入らないことが予め分かっていたりする場合には、それにフィットした効率の良いベクタを使いたい、というのが、このSRFIの目的のようです。

動作

(u8vector 0)
;=> #U8(0)

#u64(0)
;=> #u64(0)

(make-s8vector 8)
;=> #S8(0 0 0 0 0 0 0 0)

(u8vector? #u8(1 2 3))
;=> T

移植について

#u8(0)等の読み込みには、リーダーマクロを定義することになりますが、#sが構造体で使われているので競合を回避。

また、#U(0 0 0 0 ...)のような表示をするには、プリンタも変更する必要があります。残念ながらprint-objectで定義可能な部分ではないようなので処理系の関数を上書きすることになります。

CLだと、#u16(0 0 0)もu8(0 0 0)も#(0 0 0)と表示されるので区別がつかないんですねー。もちろんオブジェクトはちゃんと型どおりではあるのですが。

2011-06-13

CLでSRFI-7

| 21:46 | CLでSRFI-7 - わだばLisperになる を含むブックマーク はてなブックマーク - CLでSRFI-7 - わだばLisperになる

ちまちまとSRFIをCLに移植していますが、今回は、SRFI-7 フィーチャーベースの設定言語 です。

フィーチャーというのは、処理系がサポートしていて読み込める/読み込んでいる、ある機能のことのようですが、SRFI-7はそのフィーチャーの情報を元に、ライブラリを読み込んだり設定するための設定言語とのこと。

Schemeとは全く別の種類の言語、ということですが、混ぜて考えてはいけないことが何かあるのでしょう。

(program...)の中に、requires、files、code、feature-cond等が記述できて、これを組み合わせてライブラリを読み込みます。

requiresは必要とするフィーチャー、filesはロードするファイル名の指定、codeは、設定時に実行するコード、featture-condは、フィーチャーによって実行するものを切り分けるためのcondのようです。

手軽に書けるので、こういうのも悪くないかなと思ったり。

ついでなので、ASDFのSYSTEMを読み込めるように機能を追加してみました。

動作

(program (requires :sbcl))
;=> NIL

(program (files "/tmp/foo.lisp"
                "/tmp/bar.lisp"
                "/tmp/baz.lisp"))
;=> NIL

(program (feature-cond (:sbcl)))
;=> NIL

(program (feature-cond (:sbcl (requires :srfi-1))
                       (:srfi-0 (requires :sbcl))))
;=> NIL

(program
 (feature-cond (:allegro (systems :srfi-0 :srfi-1))
               (:sbcl (systems :srfi-2))))

(program (requires :sbcl)
         (code (print :sbcl))
         (files "/tmp/foo.lisp")
         (feature-cond (:x86-64)))
;->
;   :SBCL
;=> NIL

2011-06-12

CLでSRFI-6

| 16:17 | CLでSRFI-6 - わだばLisperになる を含むブックマーク はてなブックマーク - CLでSRFI-6 - わだばLisperになる

ぼーっとSRFIをCLに移植していますが、今回は、SRFI-6 基本的な文字列ポート です。

CLに同じ機能のmake-string-input-stream等があるので、それのエイリアスを定義しただけで終了。

2011-06-11

CLでSRFI-78

| 22:35 | CLでSRFI-78 - わだばLisperになる を含むブックマーク はてなブックマーク - CLでSRFI-78 - わだばLisperになる

ぼんやりとSRFIをCLに移植していますが、今回は、SRFI-78 軽量テスト です。

CLにもユニットテストは色々ありますが、SRFIみたいなものである程度標準化されてるというのは良いですね

基本的な使い方は、 checkの中で、式と期待する値を=>を挟んで書く、というシンプルなものです。

例のごとくシンボルの扱いが面倒臭いので、=> ではなく:=>としてみています。

便利機能としては、SRFI-42と組合さったcheck-ecというものが用意されています。

(use-package :srfi-78)

(check (+ 2 2) :=> 4)
;->
;   (+ 2 2) :=> 4 ; correct
;
;=>

(check (+ 2 2) :=> 5)
;->
;   (+ 2 2) :=> 4 ; *** failed ***
;    ; expected result: 5
;
;=>

(check-ec (:range e 100)
          (:let x e)
          (and (< e 50) (< x 50)) :=> t (e x))
;->
;   (LET ((E 50) (X 50))
;     (AND (< E 50) (< X 50))) :=> NIL ; *** failed ***
;    ; expected result: T
;

2011-06-10

CLでSRFI-48

| 19:47 | CLでSRFI-48 - わだばLisperになる を含むブックマーク はてなブックマーク - CLでSRFI-48 - わだばLisperになる

だらだらとSRFIをCLに移植していますが、今回は、SRFI-48 中級の書式文字列です。

SRFI-28にもformatがありましたが、48は機能が増えて中級ということです。CLのformatに比べたら、まだまだってことなんでしょうか。

SRFI-42や、86みたいな例もあることですし、今後のSRFIにとんでもない究極のformatが出てくることを期待しています。

しかし、SRFIって意外と似たものが複数提案される傾向があるんですね。

大体CLのformatに似た感じですが、第二引数が文字列だった場合は、文字列が返ってきたりします。

ちなみに、CLのように文字列指示文字列のところに関数を与えることはできません。

面白いと思ったのは、~Hで、ヘルプが表示されるところ。CLだったらドキュメント文字列にするところだと思いますが、Schemeにそういうものはないので一工夫ということなのでしょう。

(srfi-48:format "~H")
;=> "(format [<port>] <format-string> [<arg>...]) -- <port> is T, nil or an output-port
;   OPTION  [MNEMONIC]      DESCRIPTION     -- Implementation Assumes ASCII Text Encoding
;   ~H      [Help]          output this text
;   ~A      [Any]           (display arg) for humans
;   ~S      [Slashified]    (write arg) for parsers
;   ~W      [WriteCircular] like ~s but outputs circular and recursive data structures
;   ~~      [tilde]         output a tilde
;   ~T      [Tab]           output a tab character
;   ~%      [Newline]       output a newline character
;   ~&      [Freshline]     output a newline character if the previous output was not a newline
;   ~D      [Decimal]       the arg is a number which is output in decimal radix
;   ~X      [heXadecimal]   the arg is a number which is output in hexdecimal radix
;   ~O      [Octal]         the arg is a number which is output in octal radix
;   ~B      [Binary]        the arg is a number which is output in binary radix
;   ~w,dF   [Fixed]         the arg is a string or number which has width w and d digits after the decimal
;   ~C      [Character]     charater arg is output by write-char
;   ~_      [Space]         a single space character is output
;   ~Y      [Yuppify]       the list arg is pretty-printed to the output
;   ~?      [Indirection]   recursive format: next 2 args are format-string and list of arguments
;   ~K      [Indirection]   same as ~?
;   "

移植について

参照実装が、一関数で300行越えの塊というところに若干めげそうになりましたが、名前空間の汚染を嫌ったかなにかで一つに纏まっているだけのようなので分解して定義しました。

また、ソースがぶらさがり括弧派のコードで、Schemeにもそういう人はいるんだなと妙に感心。

2011-06-08

CLでSRFI-10

| 19:47 | CLでSRFI-10 - わだばLisperになる を含むブックマーク はてなブックマーク - CLでSRFI-10 - わだばLisperになる

きまぐれでCLに移植しているSRFIですが今回は、#,外部フォームです。

#,はリーダーマクロですが、CLの#.(リード時EVAL)に似た感じのものです。

違いとしては、CLの#.は後続の式をEVALしますが、SRFI-10の#,はAPPLYします。

ちなみに、#,はCLtL1やZetalispでは、ロード時評価のリーダーマクロでしたがANSI CLでは、load-time-valueに置き換えられて廃止になっています。

下準備

(srfi-10:enable-read-time-application)
(import 'srfi-10:define-reader-ctor)
;; CLの #.
#.(+ 3 (+ (+ 3 3) 3))
;=> 12

;; SRFI-10の#,
(define-reader-ctor '+ #'+)

#,(+ 3 #,(+ #,(+ 3 3) 3))
;=> 12

そのままでは、式のCDRが再帰的に評価されていかないので#,をネストしたりする必要があります。

単純なリード時の評価以外の主な応用としては、構造体などの簡便な表記などがあるようです。

(defstruct foo x y z)

(define-reader-ctor 'foo #'make-foo)

#,(foo :x 1 :z 2 :y 3)
;=> #S(FOO :X 1 :Y 3 :Z 2)

(defstruct bar x y z)

(define-reader-ctor 'bar
  (lambda (x y z) (make-bar :x x :y y :z z)))

#,(bar 1 2 3)
;=> #S(BAR :X 1 :Y 2 :Z 3)

クラスにreader-ctorを定義して、さらにprint-objectも設定してみる

(defclass baz ()
  ((x :initarg :x)
   (y :initarg :y)
   (z :initarg :z)))

(define-reader-ctor 'baz
  (lambda (&rest args)
    (apply #'make-instance 'baz args)))

(defmethod print-object ((obj baz) stream)
  (with-slots (x y z) obj
    (format stream "#,(BAZ :x ~S :y ~S :z ~S)" x y z)))

#,(baz :x 1 :y 2 :z 3)
;=> #,(BAZ :x 1 :y 2 :z 3)

移植について

SRFI-10の文献だけだと微妙な挙動が分からなかったためGaucheで動作を確認して大体のところはGaucheと同じような挙動にしました。

関数のルックアップは、シンボルのSYMBOL-FUNCTIONを見るようにもしてみましたが、これもGaucheの動作に合せてdefine-reader-ctorで定義したものだけ引いてくるようにしてみました。

ちなみにctorはconstructorの略のようで、調べてみたらちらほら用例があるようです。catみたいな略し方ですね。