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-06-02

CLでSRFI-28

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

移植というより単なるコピペな感じのSRFI移植ですが、今回は、SRFI-28 基本的な書式文字列です。

CLのFORMATの基本的な機能の~A、~S、~%、~~が使えるのみのシンプルなFORMATというところですが、ストリームには出力されず文字列が返ってきます。

(shadowing-import 'srfi-28:format)
;=> T

(format "foo~abaz~%" 'bar)
;=> "fooBARbaz
;   "

ちなみに、自分はCLのFORMATの書式指示子は大文字で書く派です。

MacLISPの時代から大文字で~Aと書いても小文字で~aと書いてもOKだったようですが、コードサンプルは古えから大文字が多いためか、大文字で書いている例が多いようです。

Schemeは小文字で書く人が多い気がしますが、上のSRFI-28の参照実装では小文字しか見ていないので小文字で書く必要があります。

今度無駄に統計でも取ってみようかなとも思っています。

2011-06-01

CLでSRFI-11

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

一日1SRFIな感じで進めていますが、今回は、SRFI-11 です。

SRFI-8と同じくSRFI-11も多値の束縛のための構文ですが、こんなに近い番号で似たようなものがあるというのも不思議な気もします。ざっとSRFIを眺めると当然かもしれませんが後に出て来た方が大抵は機能が豊富なようです。

それで、その機能が豊富なところですが、receiveは一つのフォームを束縛するのみでしたが、let-valuesは多段をサポートします。

(use-package :srfi-11)

(let-values (((x y z) 
              (values 1 2 3))
             ((a b c) 
              (values 1 2 3))
             ((d e f) 
              (values 1 2 3)))
  (list x y z a b c d e f))
;=> (1 2 3 1 2 3 1 2 3)

(let*-values (((x y z) 
               (values 1 2 3))
              ((x y z) 
               (values x y z))
              ((x y z) 
               (values x y z)))
  (list x y z))
;=> (1 2 3)

という風にletとlet*のお約束もそのままというところ。

多段にしたため、括弧の入れ子が1段増えてしまいますが、括弧好きな自分でも、3階層位で構造を見失いかけるのでこの辺りが限界かなと感じます。

上のように改行すれば、まだなんとか大丈夫ではありますが。

ちなみにGoogle コード検索で調べてみたところではCLでlet-valuesを自作している人はちらほらいるようです。

多段のMULTIPLE-VALUE-BINDが欲しい場合はたまにあるかもしれないですね。

2011-05-31

CLでSRFI-8

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

今回は、SRFI-8 ですが、多値の束縛のための構文です。

素のR5RSだと多値関係は、call-with-valuesで

(call-with-values
  (lambda ()
    (partition (precedes pivot) others))
  (lambda (fore aft)
    (append (qsort fore) (cons pivot (qsort aft)))))

のように書くようですが、もっと簡単に書きたいということで、

(receive (fore aft) (partition (precedes pivot) others)
  (append (qsort fore) (cons pivot (qsort aft))))

に書けるようにするもののようです。

CLで定義する場合は、MULTIPLE-VALUE-BINDと同じなので、

(setf (macro-function 'receive)
      (macro-function 'multiple-value-bind))

のように定義するのが一番簡単です。

receiveという名前は短くて便利そうなのでエイリアスを作成している人はちらほらいるのかなと思ってgoogleコード検索してみましたが、意外なことにみつけられませんでした。

mvbindとかmbindはあるようですが…。

2011-05-30

CLでSRFI-62

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

今回は、SRFI-62 S式1つ分のコメントです。

具体的にどんなものかというと

(list #; 1 2 3)
;=> (2 3)

のようなもので#;の後ろ1つだけをコメントとして扱うというものです。

CLのリーダーマクロ的には、式を1つ読んで無視すれば良いので簡単に作れます。

#;のように2文字で構成されるディスパッチ文字マクロでは、十進の引数が取れるのですが、もったいないのでSRFI-62の勝手な拡張として引数を使ってみることにしてみました。

#;は#1;の省略形で#;2は、次の2つの式を無視することになります。オーバーラップするとエラー

'(#2; a b #2;c d e)")
;=> (E)

割と便利な気もするんですが、デフォルトのSLIME上だと、;の方が勝ってしまうため、#;を意図通りに動作させるには若干の改造が必要かなと思います。

2011-05-29

CLでSRFI-16

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

今回は、SRFI-16 case-lambdaです。

拡張されていないSchemeにはCLでのラムダリスト的なものはなく、手短に書きたい時などのために簡便な方法が色々と考えられているようです。

case-lambdaは引数のパターンをcaseで判断してボディを実行するというもの。

(setf (symbol-function (intern (reverse (string 'arg-length))))
      (case-lambda ((x) 1)
                   ((x y) 2)
                   (args :many)))
;=> #<FUNCTION (LAMBDA (&REST #:G1)) {10174EF339}>

(htgnel-gra 1)
;=> 1

(htgnel-gra 1 2)
;=> 2

(htgnel-gra 1 2 3)
;=> :MANY

(htgnel-gra 1 2 3 4)
;=> :MANY

あまり見掛けない気もするので、検索してみたらcase-lambda自体の定義が殆どでした。

あまり使い勝手が良さそうでもないので、人気がないのかもしれません。

2011-05-27

CLでSRFI-61

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

SRFI-87で、=> が出てきたので、今回は、その流れで、SRFI-61です。

Schemeのcondでは、=>が使えるのは前回も書きましたが、それにガード節を加えたものがSRFI-61です。

このガード節を上手く使うことによって色々できます。

(shadowing-import 'srfi-61:cond)

(let ((alist '((a . 1) (b . 2) (c . 3))))
  (cond ((assoc 'a alist) #'values :=> #'cdr)
        (:else nil)))
;=> 1

(let ((alist '((a . 1) (b . 2) (c . 3))))
  (cond ((assoc 'z alist) #'values :=> #'cdr)
        (:else nil)))
;=> NIL

今回もキーワードは、キーワードシンボルで書くことにしてみました。

シンボルの衝突を考えなくて良いのでキーワードにしておくとやっぱり楽かなと思います。

2011-05-26

CLでSRFI-87

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

簡単そうなところからSRFIを移植していますが、今回は、SRFI-87です。

Schemeのcondでは、=>が使えて、述語が真ならば=>で指定した関数をその結果に適用できたりします。

SRFI-87は、それをcaseにも導入してみよう、というところです。

(shadowing-import 'srfi-87:case)

(case 1
  ((1 2 3 4) :=> #'values)
  ((5 6 7) (print '(5 6 7)))
  (:else :=> #'list))
;=> 1

(case 5
  ((1 2 3 4) :=> #'values)
  ((5 6 7) (print '(5 6 7)))
  (:else :=> #'list))
;->
;   (5 6 7)
;=> (5 6 7)

(case 8
  ((1 2 3 4) :=> #'values)
  ((5 6 7) (print '(5 6 7)))
  (:else :=> #'list))
;=> (8)

移植について

今回もdefine-syntaxにはmbeを利用しています。

元のSchemeのものを知っている方は、おや、と思うところがあると思いますが、シンボルのインポートがらみで、=>とelseが他のパッケージとぶつかったりすると混ぜて使うのが面倒になりそうなので、キーワードにしてしまいました。

2011-05-25

CLでSRFI-98

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

なんとなくぼんやりとSRFIを移植していますが、今回は、moshのhigepon氏でおなじみのSRFI-98です。

CLではget-environment-variableはGETENVという名前で大抵の処理系にあり、get-environment-valiablesは、ENVIRONMENTのような名前で存在することが多いようです。

使い方は、

(use-package :srfi-98)

(get-environment-variable "SHELL")
;=> "/usr/bin/zsh"

(get-environment-variables)
;=> (("STY" . "1896.pts-0.setq") ("TERM" . "vt100")
;   ......

のようなところ。

CLのGETENVの場合、SETFメソッドも付いてくることが多いのでついでに拡張してみました

(setf (get-environment-variable "FOO") "1234")

(get-environment-variable "FOO") 
;=> "1234"

2011-05-24

PRINT-OBJECTでfortune

| 22:04 | PRINT-OBJECTでfortune - わだばLisperになる を含むブックマーク はてなブックマーク - PRINT-OBJECTでfortune - わだばLisperになる

closure-htmlなどでは、オブジェクトの印字で、#.(foo ... #|comment|#)のようなものが返ってくるのですが、どういう仕掛けでリードマクロがはいったものを返してくるんだろうと不思議に思っていました。

Twitterでつぶやいていたら、print-objectでやってるんじゃないか、とのアドバイスをもらい、早速試してみたのですが、なるほど印字表現を工夫するだけで同様のことができます。

良く良く考えてみれば、#<... >もリーダーマクロです。

ということで、記念にどうでも良い機能を盛り込んだものを作成。インスタンスが評価されるたびにfortuneが実行されます。

もちろんオブジェクトの実体は印字表現とは別個のものなので変数にも普通に格納できます。

(ql:quickload :kmrcl)

(defclass foo () ())

(defmethod print-object ((obj foo) stream)
  (format stream
          "#| ~A |#~%#.(make-instance 'foo)"
          (kl:string-trim-whitespace  
            (kl:command-output "/usr/games/fortune"))))

(defvar *foo* (make-instance 'foo))

*foo*
=> #| It is a wise father that knows his own child.
   		-- William Shakespeare, "The Merchant of Venice" |#
   #.(make-instance 'foo)
=> #| A man was reading The Canterbury Tales one Saturday morning, when his
   wife asked "What have you got there?"  Replied he, "Just my cup and Chaucer." |#
   #.(make-instance 'foo)
=> #| Try to get all of your posthumous medals in advance. |#
   #.(make-instance 'foo)

CLでSRFI-2

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

なんとなくSRFI全部を移植してみたくなってきた今日この頃ですが今回は、SRFI-2です。

and-let*はたまに使いたくなります。

使い方は、

(use-package :srfi-2)

(and-let* ((pair (assoc 'a '((b . 2)
                             (a . 1)))))
  (cdr pair))
;=> 1

位のところでしょうか。もっと色々できます。

似たようなところでは、アナフォリックマクロのAAND等があります。

暗黙に束縛されるITのような変数は気持ち悪い、という場合は、and-let*が良いのではないでしょうか。

2011-05-22

CLでSRFI-5

| 13:24 | CLでSRFI-5 - わだばLisperになる を含むブックマーク はてなブックマーク - CLでSRFI-5 - わだばLisperになる

SRFIにはLET系の拡張提案は多いようですが、SRFI-5は、名前付きLETの提案で、2種類の書法があるようです。

一つは良くみる形式ですが、もう一つはあまり見掛けません。

なんのためにCLに移植するのかは自分でも良く分かりませんが、移植することにしてみました。

SRFI-5だと下のような書き方ができます。

(shadowing-import 'srfi-5:let)

(let fib ((n 10))
  (if (< n 2)
      n
      (+ (fib (1- n))
         (fib (- n 2)))))

(let (fib (n 10))
  (if (< n 2)
      n
      (+ (fib (1- n))
         (fib (- n 2)))))

最初のものはいつもの名前付きLETですが、2番目は、括弧の位置が違います。

(define fib (lambda (n) ...))

(define (fib n) ...)

の二つの書式を名前付きLETにも、ということらしいです。

移植について

新しいLETを定義するわけですが、CL:LETとぶつからないようにするには、普段あまり気にしない名前の衝突について考えて作成したり使ったりする必要があります。

また、新しいLETを作る、といっても、CL:LETを上書きしなくても大丈夫です(上書きも一つの方法ですが)

CLパッケージにあるものとぶつかるような名前は、極力使わないようにする、というのがなんとなくの定石かなとは思いますが、CLのパッケージの仕組みは十分柔軟にできていて、パッケージの扱いを良く考えれば避ける必要もないのかなと個人的には思いました。

2011-05-19

CLでSRFI-86

| 20:51 | CLでSRFI-86 - わだばLisperになる を含むブックマーク はてなブックマーク - CLでSRFI-86 - わだばLisperになる

ボツになったSRFIを眺めていて、SRFI-92 alambdaという変なものをみつけ、これはボツになりそうだなーとか思っていたら、LAMBDAの形式ではなくLETの形式であるSRFI-86はボツになってなかったので、面白そうだということでCLに移植してみることにしました。

このSRFI-86ですが、これまでのLISP系に登場した束縛系の構文の全部盛りのような感じです。

多値 & 分配束縛

muとかnuとか謎ですが、VALUES-LISTみたいな感じでしょうか

(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)
        (floor 3  4)))
  (list a b))
;=> (0 3)

名前付きLET

名前付きLETもサポートしています。名前付き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)
      (funcall 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 (zerop n)
      a
      (funcall fact (1- n) (* a n))))
;=> 3628800

;; 名前が後ろにある形式の名前付きLET
(alet (((n 10)
        (a 1) . fact))
  (if (zerop n)
      a
      (funcall fact (1- n) (* 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)
      (funcall intag d e 10)
      (if (< c 10)
          (funcall 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などがありますが、そういうのも取り込んだようです。

CLでは残念ながら脱出しかできないので、blockに変換することにしました。

SRFI-86の例をみると、継続を利用してリスタート的な機構も実現しようとしている様子…。

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

制御構文関係

たまに欲しくなるSRFI-2のand-let*ですが、そういう制御構文系のもの取り込んでいるようです。

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

lambda-list系

CLでいう&rest、&optional、&key関係ですが、その辺もサポート。CLのものより強力なのかもしれません。

;; キーワードで分配
(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)

;; 比較/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 1) (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))

;; 文字もキーにできる
(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系

名前付きLETが複数の名前を持てるように拡張されているのに、letrecに相当するものもサポート

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

その他

その他、使いたくなるのかどうか良く分からないもの

(let (a b)
  (alet ((a :a)
         (b :b)
         (() (setq a 100 b 200)))
    (list a b)))
;=> (:A :B)

≡
(let (a b)
  (setq a 100 b 200)
  (alet ((a :a)
         (b :b))
    (list a b)))
;=> (:A :B)
(let (a b)
  (alet* ((a :a)
          (b :b)
          (() (setq a 100 b 200)))
    (list a b)))
;=> (100 200)

≡
(let (a b)
  (alet* ((a :a)
          (b :b))
    (setq a 100 b 200)
    (list a b)))
;=> (100 200)
(alet ((cat '(1 -2 3)
            (a 0 (plusp a))
            (b 0 (plusp b))
            (c 0 (plusp c))
            . d))
  (list a b c d))
;=> (1 3 0 (-2))

色々複合した例

(let (m n)
  (alet* ((a (progn (princ "1st") 1))
          ((b c) 2 (progn (princ "2nd") 3))
          (() (setq m nil) (setq n (list 8)))
          ((d (progn (princ "3rd") 4))
           (key '(e 5 tmp 6) (e 0) ((f 'tmp) 55)) . p)
          g (nu (progn (princ "4th") 7) n)
          ((values . h) (apply #'values 7 (progn (princ "5th") n)))
          ((m 11) (n n) . q)
          (rec (i (lambda () (- (funcall j) 1)))
               (j (lambda ()  10)))
          (and (k (progn (princ "6th") m))
               (l (progn (princ "end") (terpri) 12)))
          (o))
    (if (< d 10)
        (funcall p 40 50 60)
        (if (< m 100)
            (funcall q 111 n)
            (progn (princ (list a b c d e f g h
                                (funcall i)
                                (funcall j)
                                k l m n))
                   (terpri))))
    (o (list 'o p q))
    (princ "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 (LAMBDA #) {101816E539}> #<CLOSURE (LAMBDA #) {101816F549}>)

移植について

移植は、define-syntaxがあるのでmbeを利用。

200行近い大きさのマクロが果して正しく動いてるかどうかは謎です。

テストのセットがあると良いのですが…。

オリジナルと違うところとしては、内部のletrec系の動作は、最初ローカル関数のlabelsでの定義に置き換えようとしていましたが、letrec+funcallにしてしまいました。

継続系は、脱出継続としてblockをあてはめました。リスタート的なものもできなくはないですが、もうちょっと構成が掴めてから挑戦してみたいと思っています。

どうもSRFI-86をサポートしているScheme処理系は少ないようですが、なんとなく分かる気もしました…。

2011-05-14

CLでSRFI-26

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

f-underscoreのようなものもあるし、熱烈に使いたいということもなくなってきたSRFI-26ですが暇だったのでCLにまるごと移植してみました。

使い勝手の問題ですが、LISP-1とLISP-2の表記上の違いがあり、デフォルトを (cut list ...)のようにするか、 (cut #'list ...)のようにするかで迷いましたが、#'を付けることにしました。

#'を付けないで書けた方が良いのですが、付けないとすると、

(mapcar (cut #'list 1 2 <> 3)
        '(1 2 3 4))
;=> ((1 2 1 3) (1 2 2 3) (1 2 3 3) (1 2 4 3))

(let ((list #'list*))
  (mapcar (cut list 1 2 <> 3)
          '(1 2 3 4)))
;=> ((1 2 1 . 3) (1 2 2 . 3) (1 2 3 . 3) (1 2 4 . 3))

のような書き分けができなくなります。

まあ、こんなこともあまりしないので第一引数は、関数であると決め打ちにしてしまっても良いかなとは思います。その場合の改造も簡単にできると思います。

移植について

移植は、define-syntaxがあるのでmbeを利用。

mbeのお蔭で殆どソースコードはいじらなくても動きました。

2011-05-13

CLでSRFI-1

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

自分的にたまーに欲しいと思うことがあるSRFI-1ですが思い切ってCLにまるごと移植してみました。

SRFI-1は過去のSchemeのライブラリやCommon Lispのリスト系の関数を調査して作成したとのことで、リスト操作で欲しそうなものは一通り揃っている気がします。

移植方法ですが、最初は、ちまちまdefineをdefunに直したりしてのんびり気長に行こうと思っていたのですが、途中で面倒になってdefineというマクロを定義してみたあたりから、オリジナルのソースをできるだけいじらない、という方針に切り替えました。

ということで字面的には、ほぼオリジナルのままです。(変更はcheck-argが微妙だったのでdeclareの型宣言で置き換えた位)

ありがちな書き換えとしては、

  • condのelse節を:elseと書くことでなんとなく対応
  • null?等をCLの関数のエイリアスとして作成
  • named letはマクロでlabelsに変形。末尾呼び出しの最適化は処理系に期待。
  • letrecは、labelsに形が近いので手書きで変形
  • defineの引数は、CLの&rest、&optionalをそのまま使う

書き換えにチャレンジしなかったところとしては、

  • functionの廃止 (lisp1化)
  • condを再定義して=>とelseが使えるようにする
  • named letを再帰除去してループに

というところです。

Scheme->CLのソースコードのトランスレータもどっかに落ちてそうなので探してみたいところではあります。

2011-05-10

lisp-criticでスタイルチェック

| 20:11 | lisp-criticでスタイルチェック - わだばLisperになる を含むブックマーク はてなブックマーク - lisp-criticでスタイルチェック - わだばLisperになる

最近LISPのスタイルチェッカーに興味があって色々調べてみたりしていますが、そういえばスタイルを批評してくれるツールがどっかにあったなと思い出して探し出してセットアップしてみました。

lisp-criticというツールなのですが大学でLISPを教える際の補助ツール的なもののようです。

どのように使うかというと、基本的にDEFUNのフォームをチェッカーに渡してチェックさせます。

(lisp-critic:critique
 (defun foo ()
   (defun bar ()
     (setq a 'b)))

のようなものを評価すると

----------------------------------------------------------------------
DEFUN's don't nest in CL like they do in Scheme.
   They're always top-level. FLET and LABELS can define local
   functions, but you don't need them here.
----------------------------------------------------------------------
GLOBALS!! Don't use global variables, i.e., A
----------------------------------------------------------------------

のような批評がでます。

配布されているパターン集が結構細かいところまで網羅されているのですが、自分でパターンを書いて拡張することもできます。

説明がなくて、セットアップがちょっと面倒なのでブログにエントリーついでにASDF化してgithubに上げてみました。

また、毎度CRITIQUEのフォームで囲うのも面倒なので、SLIMEのコンパイル時に一緒に実行するようなadviceを付けて使ってみるのも良いかなと思います。

(eval-after-load "slime"
  '(defadvice slime-compile-defun (before critique-advice activate)
    (slime-eval-async
        `(swank:eval-and-grab-output
          ,(format "(let ((*standard-output* *error-output*))
                      (lisp-critic:critique %s))"
                   (slime-defun-at-point))))))

ちょっと使ってみたところでは、CL入門者には結構良いかもしれないな、という感じです。

2011-05-07

CLでSRFI-42

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

必要だった、ということは全くなかったのですが、Scheme版LOOPマクロという評判のSRFI-42をCLに移植してみました。

SRFI-42は、define-syntaxで書かれていて、CLのDEFMACROとは違うのですが、Drai Sitaram氏のmacro by example(mbe)を使ってみたところ殆ど修正もなく移植完了。

  • mbe
  • mbe (ASDF化してgithubに置いてみたもの)

srfi-42だとこんな感じに書けます

(defun palindrome-p (list)
  (every?-ec (:parallel (:- nom list)
                        (:- rev (reverse list)))
             (equal nom rev)))

(palindrome-p '(1 2 3 2 1))
;=> T

(defun flatten (list)
  (append-ec (:- e list)
             (if (listp e)
                 (flatten e)
                 (list e))))

(flatten '(1 2 3 (4 5 (6 (7 (8 (9 (((10((((((()))))))))))))))))
;=> (1 2 3 4 5 6 7 8 9 10)

(defun taxi-number (n)
  (list-ec (:- a 1 n)
           (:- b (+ a 1) n)
           (:- c (+ a 1) b)
           (:- d (+ c 1) b)
           (if (= (+ (expt a 3) (expt b 3))
                  (+ (expt c 3) (expt d 3))))
           (list a b c d)))

(taxi-number 100)
;=> ((1 12 9 10) (2 16 9 15) (2 24 18 20) (2 34 15 33) (2 89 41 86) (3 36 27 30) (3 60 22 59) (4 32 18 30) (4 48 36 40) (4 68 30 66) (5 60 45 50) (5 76 48 69)
     (6 48 27 45) (6 72 54 60) (7 84 63 70) (8 53 29 50) (8 64 36 60) (8 96 72 80) (9 34 16 33) (9 58 22 57) (10 27 19 24) (10 80 45 75) (11 93 30 92)
     (12 40 31 33) (12 51 38 43) (12 96 54 90) (15 80 54 71) (17 39 26 36) (17 55 24 54) (17 76 38 73) (18 68 32 66) (20 54 38 48) (20 97 33 96) (23 94 63 84)
     (24 80 62 66) (24 98 63 89) (29 99 60 92) (30 67 51 58) (30 81 57 72) (34 78 52 72) (35 98 59 92) (42 69 56 61) (47 97 66 90) (50 96 59 93) (51 82 64 75))

オリジナルと違うところとしては、 (: i 10)のようなものはCLでは不可なので、 (:- i 10)のようにして回避しました。

また、えぐいところとしては、キーワードシンボルにマクロが定義されることになります;(:- i 10)もマクロだったり。

ちなみに実行は関数呼び出しの連発になるので通常のLOOPと比べると25倍位遅い(SBCL調べ)ようですが、この辺りを高速化するのも盆栽的に面白いかなと思っています。

また、繰り返しは再帰なのですが、末尾再帰を最適化しない処理系では厳しいかもしれません。(SBCLは最適化するので大丈夫なようですが。)