#:g1: YTools: bindersの紹介

Posted 2014-11-12 15:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の317日目です。

YTools: bindersとはなにか

 YTools: bindersは、Drew McDermott氏のユーティリティであるYtoolsの中の束縛構文と繰り返し系のユーティリティです。

パッケージ情報

パッケージ名YTools: binders
Quicklisp×
プロジェクトページHome page for Drew McDermott
CLiKiCLiki: YTools

インストール方法

 上記プロジェクトページからダウンロードしてきて適当に導入します。

試してみる

 まず束縛構文ですが、ローカル関数を束縛するlet-funとローカル変数のlet-varsがあります。

let-fun

 let-funはlabelsに相当し、fletに相当するものにはlet-fun-nonrecというのがあります。
let-funで面白いのは、:where句が使えることで関数定義を後置可能です。
:where句の関数定義の組み合わせにより好みに応じて書き方を選べます。

(defun fib (n)
  (let-fun ((fib (n a1 a2)
              (cond ((zerop n) a2)
                    ((onep  n) a1)
                    (T (fib (1- n) (+ a1 a2) a1)))))
    (fib n 1 0)
   :where
    (:def onep (n) (= 1 n))))

(defun fib (n) (let-fun ((:def onep (n) (= 1 n))) (fib* n 1 0) :where (:def fib* (n a1 a2) (cond ((zerop n) a2) ((onep n) a1) (T (fib* (1- n) (+ a1 a2) a1))))))

(defun fib (n) (let-fun () #'fib* (fib* n 1 0) :where (:def fib* (n a1 a2) (cond ((zerop n) a2) ((onep n) a1) (T (fib* (1- n) (+ a1 a2) a1)))) (:def onep (n) (= 1 n))))

let-var

 let-varがいまいち良く分かりませんが、こんな感じです。
あまり面白味はないかも。

(let-var (((x 0) (y 0)))
  (list x y)
 :where (setq x 4))
;=>  (4 0)

repeat

 Common LispのLOOPの複雑さとLispらしくなさに対して考えられたのがrepeatです。
といっても十分複雑な気がします。
一番シンプルな使い方はこんな感じです。

(repeat :for ((i = 0 :to 9))
  :collect i)
;=>  (0 1 2 3 4 5 6 7 8 9)

これだとほぼLOOPと変わりありません。
LOOPのINTO的なものを実現するには:collector(s)と:intoの組み合わせを利用します。

(repeat :for ((i = 0 :to 9)
              :collector is)
  :collect (:into is i)
  :result is)

LOOPだとサブフォームに通常のLispフォームが出てきたら、そこから内側はLOOPの構文は使えませんが、repeatでは、:withinと:continueの組み合わせでこれを実現可能です。

(repeat :for ((i = 0 :to 9)
              :collectors odds evens)
  :within
  (if (oddp i)
      (:continue :collect (:into odds i))
      (:continue :collect (:into evens i)))
  :result (append odds evens))
;=>  (1 3 5 7 9 0 2 4 6 8)

さらに、:whereで関数定義を後置可能です。

(repeat :for ((i = 0 :then (add1 i))
              :collectors odds evens)
  :while (< i 10)
  :within
  (if (oddp i)
      (:continue :collect (:into odds i))
      (:continue :collect (:into evens i)))
  :result (append odds evens)
  :where (:def add1 (i) (+ 1 i)))

また細かいところでは、変数更新の所で同じフォームが来る場合に:againで略記が可能です。

(with-input-from-string (in "foo bar baz")
  (repeat :for ((c = (read-char in nil) :then :again))
    :while c
    :collect (char&code c)
    :where (:def char&code (c)
             (list c (char-code c)))))
;=>  ((#\f 102) (#\o 111) (#\o 111) (#\  32) (#\b 98) (#\a 97) (#\r 114) (#\  32)
;     (#\b 98) (#\a 97) (#\z 122))

 LOOPを使って記述密度が高く書けているrebinding(once-only)のコードでも比較してみます。

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

(defmacro rebinding (vars &body body) (repeat :for ((var :in vars) :collectors renames temps) :within (let ((name (gensym (symbol-name var)))) (:continue :collect (:into renames `(,name ,var)) :collect (:into temps ``(,,var ,,name)))) :result `(let ,renames (*:with-unique-names ,vars `(let (,,@temps) ,,@body)))))

repeatだと束縛部でシリアルな束縛(letに対するlet*)ができないので、letと:withinを使ってみています。letでなくてもsetqでも良いのですが。
まあそこそこの記述密度ではないでしょうか。

まとめ

 今回は、YTools: bindersを紹介してみました。
:withinと:continueのお蔭でLOOPよりはrepeatの方が既存のLisp構文と混ぜて使える感じはしますね。

comments powered by Disqus