LetSの紹介 — #:g1

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

(LISP Library 365参加エントリ)

 LISP Library 365 の97日目です。

LetSとはなにか

 LetSは、Richard C. Waters氏作の繰り返し系のライブラリです。
1982年に作られたものですが、Seriesの前身にあたります。

パッケージ情報

パッケージ名LetS
Quicklisp×
参考文献[AIM-680a] LetS: An Expressional Loop Notation

インストール方法

 ITSのアーカイブを探して、LIBDOC;LETS RCW1を見付け、お好みの処理系で動かしましょう。
元のソースには#Mや、#Qの記述があるので、MacLISPとLispマシン用だったと思われます。
ちなみに私がCommon Lispに移植したものがありますので、良かったらどうぞ

試してみる

 大体のところは、後に登場するSeriesと同じです。
Seriesでいうscan系のものは、Eの接頭辞が付き、collect系のものには、Rの接頭辞が付きます。

(Rlist (Esublists '(1 2 3 4)))
;=>  ((1 2 3 4) (2 3 4) (3 4) (4))

(defun my-copy-list (list) (Rlist (Elist list)))

(my-copy-list '(1 2 3 4)) ;=> (1 2 3 4)

(defun zero-vector (vec) (Rvector vec (Gsequence 0)))

(zero-vector #(1 2 3 4 5 6)) ;=> #(0 0 0 0 0 0)

(Rlist (mapS #'+ (Gsequence 100) (Erange 0 10))) ;=> (100 101 102 103 104 105 106 107 108 109 110)

(Rlist (filterS #'oddp (Erange 1 20))) ;=> (1 3 5 7 9 11 13 15 17 19)

(Rvector (make-array 20) (generateS #'1+ 1)) ;=> #(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20)

letS*

 LetSの特徴的な構文がletS*です。
sequence(Seriesでいうseries)を通常の変数を扱うように書くことができます。

(defun square-alist (alist)
  (letS* ((entry (Elist alist))
          (square (* (cdr entry) (cdr entry))))
    (Rlist (cons (car entry) square))))

(square-alist '((A . 1) (B . 2) (C . 3) (D . 4) (E . 5) (F . 6) (G . 7) (H . 8) (I . 9) (J . 10) (K . 11) (L . 12) (M . 13) (N . 14) (O . 15) (P . 16) (Q . 17) (R . 18) (S . 19) (T . 20) (U . 21) (V . 22) (W . 23) (X . 24) (Y . 25) (Z . 26))) ;=> ((A . 1) (B . 4) (C . 9) (D . 16) (E . 25) (F . 36) (G . 49) (H . 64) (I . 81) (J . 100) (K . 121) (L . 144) (M . 169) (N . 196) (O . 225) (P . 256) (Q . 289) ; (R . 324) (S . 361) (T . 400) (U . 441) (V . 484) (W . 529) (X . 576) (Y . 625) (Z . 676))

 分配束縛も可能

(defun square-alist-destructuring (alist)
  (letS* (((key . i) (Elist alist)))
    (setq i (* i i))
    (Rlist (cons key i))))

(square-alist-destructuring '((A . 1) (B . 2) (C . 3) (D . 4) (E . 5) (F . 6) (G . 7) (H . 8) (I . 9) (J . 10) (K . 11) (L . 12) (M . 13) (N . 14) (O . 15) (P . 16) (Q . 17) (R . 18) (S . 19) (T . 20) (U . 21) (V . 22) (W . 23) (X . 24) (Y . 25) (Z . 26))) ;=> ((A . 1) (B . 4) (C . 9) (D . 16) (E . 25) (F . 36) (G . 49) (H . 64) (I . 81) (J . 100) (K . 121) (L . 144) (M . 169) (N . 196) (O . 225) (P . 256) (Q . 289) ; (R . 324) (S . 361) (T . 400) (U . 441) (V . 484) (W . 529) (X . 576) (Y . 625) (Z . 676))

 ちなみにマクロ展開すると普通の繰り返しになっているのが分かります。

(BLOCK T
  (LET (#:REVLIST9 KEY I #:OUT5 #:LIST4 #:MAP12)
    (TAGBODY
      (SETQ #:LIST4 ALIST)
     #:LETS/L02125
      (IF (NULL #:LIST4)
          (PROGN (GO #:LETS/E02126))
          NIL)
      (SETQ #:OUT5 (CAR #:LIST4))
      (LET ((#:LIST0 #:OUT5))
        (SETQ KEY (CAR #:LIST0))
        (SETQ #:LIST0 (CDR #:LIST0))
        (SETQ I #:LIST0))
      (SETQ I (* I I))
      (SETQ #:MAP12 (CONS KEY I))
      (SETQ #:REVLIST9 (CONS #:MAP12 #:REVLIST9))
      (SETQ #:LIST4 (CDR #:LIST4))
      (GO #:LETS/L02125)
     #:LETS/E02126
      (SETQ #:REVLIST9 (NREVERSE #:REVLIST9))
      (RETURN-FROM T #:REVLIST9))))

 すこし捻った例として、rebinding(once-only)を書いてみます。
LOOPを使った例をLetSに移植

(defmacro rebinding (vars &body body)
  (loop :for var :in vars
	:for name := (gensym (symbol-name var))
	:collect `(,name ,var) into renames
	:collect ``(,,var ,,name) into temps
	:finally (return `(let ,renames
		  	    (with-unique-names ,vars
                              `(let (,,@temps)
                                 ,,@body))))))

LOOPでは、renamesと、tempsに束縛部の変数を集めて、最後に展開しますが、LetSでは

(defmacro rebinding-lets (vars &body body)
  (letS* ((var (Elist vars))
          (name (gensym (symbol-name var))))
    `(let ,(Rlist `(,name ,var))
       (*:with-unique-names ,vars
         `(let (,,@(Rlist ``(,,var ,,name)))
            ,,@body)))))

と書けます。
テンプレート内でsequenceが展開されているような感じです。

(rebinding-lets (a b)
  `(* ,a ,b))

(LET ((#:A83 A) (#:B84 B)) (*:WITH-UNIQUE-NAMES (A B) `(LET ((,A ,#:A83) (,B ,#:B84)) (* ,A ,B))))

LetSの方が展開形に近く直感的に書けているかなと思います。

まとめ

 今回は、LetSを紹介してみました。
後継のSeriesでは、もう少し遅延シークエンス+関数の組み合わせ的な記述の色が濃くなっているように思われますが、LetSでは繰り返し構文の色が強いように思えます。
LetSを少し試してみた感想としては、構文としての全体的な使いやすさは、Seriesより使いやすいかなと思ったりしています。
使いやすさの理由ですが、

等が挙げられるかなと思います。implicit mapSに関しては、Seriesでもimplicit mapの機能がありますが、いまいち中途半端な扱いになっているような印象があります(ドキュメント等でもあまり触れられていない)
そのうちSeriesとも比較した記事か何かを書いてみたいところです。

comments powered by Disqus