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 |

2009-10-31

KMRCLを眺める (3) aif

| 17:33 | KMRCLを眺める (3) aif - わだばLisperになる を含むブックマーク はてなブックマーク - KMRCLを眺める (3) aif - わだばLisperになる

今日は、KMRCLのmacros.lispの中からAIFです。

AIFはOn Lispでもお馴染のPaul Graham氏作のマクロの中でも、最も有名なものじゃないかと思います。

アナフォリック・マクロの代表という感じですが、アイデアは単純で、テストの結果をitという名前の変数に束縛するというものです。

名前は、Anaphoric IFの略。

使われ方としては、

(AIF (PROBE-FILE "/etc/passwd")
     IT
     (ERROR "No such file or directory"))
;=> #P"/etc/passwd"

みたいな感じでしょうか。

定義はこんな感じです。

;; Anaphoric macros

(defmacro aif (test then &optional else)
  `(let ((it ,test))
     (if it ,then ,else)))

testの内容をitに束縛してるというそのままな内容ですね。

利用上の注意としては、便利だからといってマクロを書くのにAIFを使ったりすると知らぬ間にitが内部で参照できるようなマクロができてしまったりするので、明確な意図がない限りはそういう目的には使わない方が良いのではないかと思います。まあ、当たり前ですね☺

KMRCLを眺める (2) let-if

| 00:37 | KMRCLを眺める (2) let-if - わだばLisperになる を含むブックマーク はてなブックマーク - KMRCLを眺める (2) let-if - わだばLisperになる

毎日KMRCLからマクロ/関数を1つずつだらだらと読んでいこうと思っていましたが、早速2、3日間が空いてしまいました。

let-if

今日は、前回のlet-whenと殆ど同じな、let-ifです。

使われ方としては、

(LET-IF (FILE (PROBE-FILE "/etc/passwd---"))
        FILE
        (PRINT "No such file or directory"))
;=> "No such file or directory"

みたいな感じでしょうか。

マクロ定義はこんな感じです。

(defmacro let-if ((var test-form) if-true &optional if-false)
  `(let ((,var ,test-form))
      (if ,var ,if-true ,if-false)))

そのままですね。

if-falseでnilが束縛されたvarが使いたいかというと、そういうこともあまり無いような気もしますが、使いたいこともあるでしょう。

どういう風にインデントして書くか微妙ですが、とりあえず、IFと同じような感じに揃えてみました。

let-when、when-let、let-if等は、単に並べ替えのマクロなので初めて書いてみたりするマクロとして良いかもしれません。

ちなみに、これらは、Emacs Lispでもそのままのマクロ定義で動くと思います。

※optional引数があるので、Emacs lispでは、(require 'cl)してdefmacro*を使う必要はあるようです。

2009-10-28

KMRCLを眺める (1) let-when

| 23:09 | KMRCLを眺める (1) let-when - わだばLisperになる を含むブックマーク はてなブックマーク - KMRCLを眺める (1) let-when - わだばLisperになる

ぼーっとしているとブログを更新しないでいるうちに月日が過ぎていってしまうので、何か更新が簡単にできそうなネタはないかと考えたのですが、日頃自分が良く使っているKMRCLの関数/マクロのソースを拾い読みして考えたことを書き散らかしてみることにしました。

KMRCLとは、Kevin M. Rosenbergさんの便利ユーティリティ集です。

Kevin M. Rosenbergさんは、DebianのCommon Lisp系のパッケージのメンテナでもあるようで、Debian/UbuntuのCL回りが嫌に充実しているのもこの方のお蔭なのかもしれません。ありがたや。

let-when

今日は、macros.lispのlet-whenを眺めてみます。

使い方としては、

(LET-WHEN (I (PROBE-FILE "/etc/passwd"))
  I)
;=> #P"/etc/passwd"

こんな感じです。

マクロの中身は、こんな感じです。

(defmacro let-when ((var test-form) &body body)
  `(let ((,var ,test-form))
      (when ,var ,@body)))

つまり、乱暴に言ってしまうならば、

(LET ((X (....)))
  (WHEN X 
    ....))

と書くのが面倒臭いので一体化してみた、という感じです。

最近のLISP方言である、Arcのwhenlet、Clojureのwhen-letやPaul Graham氏で有名なアナフォリックマクロのaifも似たようなものです。

Clojureのwhen-letについては、作者さんがアナフォリックマクロが好きじゃないようで、aifのようなマクロが作りにくく(できない?)なっているのですが、Clojureにaifはないのかという質問にwhen-letを使ったら良いじゃないかと、どこかで答えていたのを読んだ記憶があります。

また関連するところとしては、その他KMRCLのようなユーティリティ集には往々にしてwhen-letというものがあり、

(LISPWORKS:WHEN-LET (I (PROBE-FILE "/etc/passwd"))
  I)
#P"/etc/passwd"

まったく同じものが殆どです。

なぜマクロにするのかを考える

LISPのようなマクロがある言語だと、頻出するパターンは今回のlet-whenのように纒められます。

面倒臭いのでマクロに纒めていると、いつの間にやら、そいういう風に考えるようになってしまいます。

マクロの乱用について

マクロについては、乱用されるとわけがわからなくなる、という話をよく耳にしますが、マクロが自然に使える言語でこういうことを言っている人も少ない気がするので単に食わず嫌いな気がしています。

単純に上のlet-whenのパターンをlet-whenという名前をつけて利用するか、名前を付けないで毎回このパターンを手書きするかの違いだけではないでしょうか。

また、LISPでは、乱用できる機能については大抵、偉大な先達が乱用の限りを尽しているので、自重する必要も意味も大してないのではないかと私は思います…。(LOOP、FORMAT、その他)

2009-10-19

悲しいけど、これで間にあっちゃったのよね…

| 22:36 | 悲しいけど、これで間にあっちゃったのよね… - わだばLisperになる を含むブックマーク はてなブックマーク - 悲しいけど、これで間にあっちゃったのよね… - わだばLisperになる

なんらかの入力を二つに分けて文字列のリストにしたいという場面に遭遇したのですが、汚ないなーと思いながらも

; "0123456789...." ⇒
(LET (ANS)
  (PUSH
   (WITH-OUTPUT-TO-STRING (A-OUT)
     (PUSH
      (WITH-OUTPUT-TO-STRING (B-OUT)
        ...
        (PRINC I (IF PRED A-OUT B-OUT)))
        ...
      ANS))
   ANS))
;⇒ ("02468..." "13579...")

のように書いて間に合わせてしまいまいした。

(WITH-OUTPUT-TO-STRINGS (A-OUT B-OUT)
  ...
  (PRINC I (IF (EVENP I) A-OUT B-OUT))
  ...
  ))

みたいなマクロを定義するのも、そんなに遭遇するパターンでもないし面倒臭かったんですが、こういうのをすっきり書ける方法ありますでしょうか('-'*)

SERIESかな?

2009-10-17

TAOの!再び

| 03:08 | TAOの!再び - わだばLisperになる を含むブックマーク はてなブックマーク - TAOの!再び - わだばLisperになる

以前からTAOの!についてCommon Lispで再現すべく色々考えてみたりしていましたが、

TAOでは!文字の解釈が

  • SETF的な!
(let (x)
  (!x 30)
  x)
;⇒ 30
  • 自己投入式(Algol系言語でいう x += 1のような表現)
(let ((x 0) (y 1))
  (!!+ 30 y !x)
  x)
;⇒ 31
  • ORにバックトラック機能がついたようなもの(詳細不明:Prolog風らしい)
(! _x _y)

Prolog的な構文でのカット記号

(assertz (p a1..) ... B2 ! B3 ...)
  • cdr!: (setq list (cdr list))と同じ
(cdr! list)

と色々あるので、なかなか!をマクロ文字にする方法では全部を簡単に解決できません。

ということで、今度は開き括弧の方に細工をしてみようということで、ちょっと書いてみました。

Named Readtablesが面白そうだったので意味なく使ってみています。

(IN-PACKAGE :TAO-COMPAT)

(DEFUN TAO-READ-LIST (STREAM IGNORE)
  (CASE (PEEK-CHAR T STREAM)
    ((#\!) (READ-CHAR STREAM)
     (CASE (PEEK-CHAR NIL STREAM)
       ((#\Space #\Newline #\Return #\Tab)
        (READ-CHAR STREAM)
        `(OR ,@(SB-IMPL::READ-LIST STREAM IGNORE)))
       ((#\!) 
        (READ-CHAR STREAM)
        `(TAO-COMPAT:SELFASS 
          ,@(SB-IMPL::READ-LIST STREAM IGNORE)))
       (OTHERWISE 
        `(SETF ,@(SB-IMPL::READ-LIST STREAM IGNORE)))))
    (OTHERWISE
     (SB-IMPL::READ-LIST STREAM IGNORE))))

;;;
;;;
(IN-PACKAGE :CL-USER)
(USE-PACKAGE :NAMED-READTABLES)

(DEFREADTABLE :TAO-COMPAT
  (:MERGE :STANDARD)
  (:MACRO-CHAR #\( #'TAO-COMPAT::TAO-READ-LIST 'T)
  (:CASE :UPCASE))
(IN-READTABLE :COMMON-LISP)

(LET ((X (LIST 1 2 3)))
  (!!CDR !X))
;>>> error

(IN-READTABLE :TAO-COMPAT)

(! FOO BAR BAZ) ;(OR FOO BAR BAZ)に展開される

(LET ((X (LIST 1 2 3)))
  (!!CONS 'HEAD !X)
  X)
;⇒ (HEAD 1 2 3)

(LET ((X (LIST 1 2 3)))
  (!(CAR X) 'HEAD)
  X)
;⇒ (HEAD 2 3)

;; incf的
(LET ((X 0))
  (!!1+ !X)
  X)
;⇒ 1

(IMPORT 'TAO-COMPAT:CDR!)

(LET ((X (LIST 1 2 3)))
  (CDR! X)
  X)
;⇒ (2 3)

(!(SYMBOL-FUNCTION 'FOO) (LAMBDA (X) X))

(FOO 3)
;⇒ 3

やはりこの場合は、開き括弧の文字に定義されている関数を再定義する方が綺麗にできるようです。

括弧の意味の再定義はなんとなく抵抗がありますが…。

2009-10-11

祝SBCL 10周年と受け継がれるコード

| 01:45 | 祝SBCL 10周年と受け継がれるコード - わだばLisperになる を含むブックマーク はてなブックマーク - 祝SBCL 10周年と受け継がれるコード - わだばLisperになる

SBCLのプロジェクトが開始されてから十周年だそうで、記念にワークショップが開かれるようです。

SBCLはCMUCLが元になっていることは良く知られていると思いますが、CMUCLも元を辿るとSpice Lispを起源としています。

そのSpice Lispは、元々1980年位にCMUのSpiceプロジェクトで開発されていたMacLISP系のLISPだったようですが、丁度、同じ頃Common Lispも策定が開始していて、CLtL1が出た(1984年3月)頃には、

マニュアルの中で

Spice Lisp is the implemantaion of the Common Lisp
for microcodable personal machine running CMU's
Spice computing environment.

と説明されていますので、いつの間にやらCLになっていたようです。

現在、Spice Lispのソースコードはネット上には見当たらないのですが、Spice Lispから枝分かれした、PDP-20上で動くRutgers Common Lispのソースは公開されています。

これを眺めていると気付くと思うのですが、現在のSBCLにも使われているコードが結構あります。

例えば、リスト系の関数定義、list.lispを眺めてみると(同じファイル名)

;; SBCL
(defun revappend (x y)
  #!+sb-doc
  "Return (append (reverse x) y)."
  (do ((top x (cdr top))
       (result y (cons (car top) result)))
      ((endp top) result)))

;; Spice Lisp (TOPS-20 Common Lisp)
(defun revappend (x y)
  "Returns (append (reverse x) y)"
  (do ((top x (cdr top))
       (result y (cons (car top) result)))
      ((atom top) result)))

のように、殆ど同じ定義だったりします。(リスト系の関数ということもあるのでしょうが…)

というわけで、SBCLは10周年ですが、Common Lispの歴史と同じか、それより古いコードが元になっていたりもするようです。

ちなみに、LispマシンがあったMITではLOOPマクロが好んで使われていたようなのですが、CMUではLOOPは使われていなかったらしく、このlist.lispでも嫌になる程DOが多用されています。

考えつく限りの方法でDOが使い倒されているので、Spice Lisp由来のコードはDO好きにはまさにバイブル的存在といえましょう…。

2009-10-08

謎のこだわり 'Tと()

| 00:22 | 謎のこだわり 'Tと() - わだばLisperになる を含むブックマーク はてなブックマーク - 謎のこだわり 'Tと() - わだばLisperになる

全く誰も興味がない話題だと思うのですが、MacLISPのソースコードを眺めているとどういう訳か、NILという表記が少なく、()と書かれている割合が多いようです。

そして、Tは、クォートされて'Tと書かれていることが多い気がします。

どんな感じかというと

(DEFUN CHMP1 (X)                                ;"CHOMP" one function
       (SETQ DATA (GETL X '(EXPR FEXPR)) CFVFL () LAPLL () )
       (COMPILE X (CAR DATA) (CADR DATA) () () )
       (LAP-A-LIST (SETQ LAPLL (NREVERSE LAPLL)))
       (AND (COND ((SYSP X) 
                   (AND (SETQ DATA (GETL X '(EXPR FEXPR SUBR FSUBR LSUBR)))
                        (MEMQ (CAR DATA) '(EXPR FEXPR))
                        (SETQ DATA '(SUBR FSUBR LSUBR))))
                  ('T (AND (SETQ DATA (GETL X '(*EXPR *FEXPR *LEXPR SUBR FSUBR LSUBR)))
                           (MEMQ (CAR DATA) '(SUBR FSUBR LSUBR))
                           (SETQ DATA '(*EXPR *FEXPR *LEXPR)))))
            (SETQ DATA (CAR (GETL X DATA)))
            (PUTPROP X (CAR (REMPROP X DATA)) DATA)))

のような感じなのですが、condのelse節に'Tが使われていて、nilと書きそうなところも()です。

どうもMacLISPのメインメンテナだったJonL White氏が触ったところは、こういう風にNIL ⇒ ()、T ⇒ 'Tのスタイルで書かれているんじゃないかと思われ、全体として()の割合が多くなっている気がするのですが、一体どういう理由でこうなのかが謎です。

ちょろっとある更新記録にも

;;; 01/29/81 jonl - flushed (STATUS FEATURE MACAID) and NIL --> () 
;;;                 added special declaration for GRIND-MACROEXPANDED
;;; 05/25/78 jonl - Had GRINDEF and SPRINTER lambda-bind variable LINEL, and 
;;;                 removed all references to CONNIVER and PLANNER stuff.
;;;                 Flush "SGPLOSES" and NOARGS calls; flush GBREAK.
;;;                 Change "NIL" into "()".

などと書かれています。そんなに()が好きなのか。もしくは、NILが嫌いなのか。GRINDEFのファイルだけにプリティプリンタの挙動か。それとも単なる偶然か。

MacLISPのソースは、

にありますので、興味のある方は覗いてみて下さい。

また、真相をご存知の方は是非教えて下さい!

ちなみに、Tにクォートを付けるのは真似してやってみたら割と癖になってしまい、自分がLISPを書き始めた頃から大体これで書くようになってしまいました。CONDのelse節に使うと目立つのが気に入ってますが、他に良いことは別にないです。あえて言うなら、クォートを付けると私の中ではブール値っぽさが増します。NILも全部()で書いてみたら何か発見があるのかもしれない…。

2009-10-05

Scheme風のCAR/CDR

| 04:09 | Scheme風のCAR/CDR - わだばLisperになる を含むブックマーク はてなブックマーク - Scheme風のCAR/CDR - わだばLisperになる

Common Lispでは、(CAR () ) ⇒ NIL、(CDR () ) ⇒ NILになります。

これは、MacLISPINTERLISPから受け継いだ伝統で、実用上は一々エラーにならなくて便利だったりするのですが、自分は、Schemeのようにエラーになってくれた方が気付きにくいバグを見付けやすいので好きだったりもします。

Schemeのようにエラーにする良い方法はないかなと思っていたのですが、こういう時には、THEが上手く使えるんじゃないかということでメモ。

(LET ((LIST () ))
  (CAR (THE CONS LIST)))
;>>> The value NIL is not of type CONS. 
 
(LET ((LIST () ))
  (CDR (THE CONS LIST)))
;>>> The value NIL is not of type CONS. 

ちなみに、THEがエラーを上げるかどうかは処理系依存みたいです。

全然関係ないですが、HyperSpecを眺めていたら、THEはSETFとも組合せられるとのこと。

(LET (X)
  (SETF (THE INTEGER X) 30))
;⇒ 30

(LET (X Y)
  (SETF (THE (VALUES INTEGER SYMBOL) (VALUES X Y)) 
        (VALUES 30 'FOO)))
;⇒ 30,
;   FOO

なるほどー。

2009-10-04

VALUESとSYMBOL-FUNCTIONとSETFの組合せ

| 00:23 | VALUESとSYMBOL-FUNCTIONとSETFの組合せ - わだばLisperになる を含むブックマーク はてなブックマーク - VALUESとSYMBOL-FUNCTIONとSETFの組合せ - わだばLisperになる

変数を共有して関数を定義したい場合など

(LET ((X 0))
  (DEFUN UP () (INCF X))
  (DEFUN DOWN () (DECF X))
  (DEFUN VALUE () X))

;; 実行例
(LIST (UP)
      (DOWN)
      (DOWN)
      (DOWN)
      (VALUE))
;⇒ (-1 -2 -3 -4 -4)

という風に書いたりしますが、

(SETF (VALUES (SYMBOL-FUNCTION 'UP)
              (SYMBOL-FUNCTION 'DOWN)
              (SYMBOL-FUNCTION 'VALUE))
      (LET ((X 0))
        (VALUES (LAMBDA () (INCF X))
                (LAMBDA () (DECF X))
                (LAMBDA () X))))

とも書けるみたいなのでメモ。冗長なのであまり意味がないかも…。

2009-10-03

Common Lispで日常のちょっとした仕事

| 23:37 | Common Lispで日常のちょっとした仕事 - わだばLisperになる を含むブックマーク はてなブックマーク - Common Lispで日常のちょっとした仕事 - わだばLisperになる

昔のブログのエントリーを新しくWordPressに移行するためにデータの中のURLを書き換える、というちょっとした仕事があったのですが、最初sedでやってみました。

一緒に仕事をしているWebデザイナーさんに

「やっぱりLISPでやったんですか?」と訊かれたんですが、

「いや、これは別の言語でやりました」

と答えたのが妙に心残りだったのでCLで書いてみました(笑)

やりたいことは、

MovableType形式のバックアップデータのエントリー中の画像リンクのURLの変換です。

"http://example.com/imgs/1/2/3/abcd.jpg"という形式なのですが、

"http://new.example.com/wp-content/uploads/imgs/1_2_3_abcd.jpg"という風に直します。

いざ、書いてみると、sed並に簡単に書けたりするので割と悪くないなと思います。

ただ、ちょっとしたものを書くにはwith-open-fileは長いので、Arc風にw/outfileなどのマクロを定義して置くともっと手軽に書き捨てられるかと。

…というか多少面倒でもCLで書いて行こうと誓いました(笑)

どういう作業形態になるかというと、下記のようなものをSlime上のバッファに書いて、適当に評価するという感じになります。

Emacsのスクラッチと大体似た感覚で、UNIXのシェルで色々やるのとは、ちょっと違った感じですが、慣れるとこれはこれで良い感じです。

;; ファイルを文字列のリストとしてとりこむ
(DEFVAR *OLD-BLOG* (KMRCL:READ-FILE-TO-STRINGS "/home/mc/Desktop/blog-backup.txt"))

;; CL-PPCRE:REGEX-REPLACE-ALLで使う関数("/"を"_"に変換する)
(DEFUN /->_ (MATCH 1ST 2ND)
  (DECLARE (IGNORE MATCH 1ST))
  (FORMAT NIL "~A" (SUBSTITUTE #\_ #\/ 2ND)))

;; これを評価
(LET ((SCANNER (PPCRE:CREATE-SCANNER "\"\\s*http://(image.)*example.com/imgs/([^\"]*)\""))
      (REPLACE (LIST "\"/wp-content/uploads/imgs/" #'/->_ "\"")))
  (WITH-OPEN-FILE (OUT "/tmp/result.txt" :DIRECTION :OUTPUT :IF-EXISTS :SUPERSEDE)
    (DOLIST (LINE *OLD-BLOG*)
      (FORMAT OUT 
              "~A~%"
              (PPCRE:REGEX-REPLACE-ALL SCANNER LINE REPLACE :SIMPLE-CALLS 'T)))))