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

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-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は最適化するので大丈夫なようですが。)