#:g1: special-caseの紹介

Posted 2014-02-28 15:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の60日目です。

special-caseとはなにか

 special-caseは、Alex Shinn氏作の場合分けのためのちょっとしたマクロユーティリティです。

パッケージ情報

パッケージ名special-case
eggseggref/4/special-case - The Chicken Scheme wiki

インストール方法

 chicken schemeなら、

$ chicken-install special-case

でOKです。

試してみる

 なぜかchickenではなく、sagittariusで試してみます。
module節をlibraryに書き換えれば、そのまま動きます。

がこのユーティリティで提供されているものです。


(import (time)
        (srfi :42)
        (srfi :1)
        (eggs special-case))

(define (member/ key ls :optional (eq equal?)) (with-special-cased-procedures ((eq eq?)) (let lp ((ls ls)) (cond ((null? ls) #f) ((eq key (car ls)) ls) (else (lp (cdr ls)))))))

のwith-special-cased-proceduresは、


(if (and (eq? eq eq?))
  (letrec-syntax
    ((eq (lambda (x)
           (syntax-case
             x
             ()
             ((dummy . args) (syntax (eq? . args)))))))
    (let ()
      (let lp ((ls ls))
        (cond ((null? ls) #f)
              ((eq key (car ls)) ls)
              (else (lp (cdr ls)))))))
  (let ()
    (let lp ((ls ls))
      (cond ((null? ls) #f)
            ((eq key (car ls)) ls)
            (else (lp (cdr ls)))))))

のように展開されます。eq?の時だけ特別なことができるという寸法です。
この例ではいまいち有難味が感じられませんが、コンパイルがからんでくると効率が違ってきたりするのでしょうか。
with-all-special-cased-proceduresは、さらに複数の述語を指定可能で各々の組み合わせがマクロ展開されます。

eq?を指定すれば当然速くなりますが、これはマクロの効果というよりeq?を使っているからですね。

(time
 (do-ec (: i 1000000)
        (member/ 'z '(a b c z))))
;;  (do-ec (: i 1000000) (member/ 'z '(a b c z)))
;;  0.646777 real    0.644041 user    0.000000 sys
#<unspecified>

(time (do-ec (: i 1000000) (member/ 'z '(a b c z) eq?)))

;; (do-ec (: i 1000000) (member/ 'z '(a b c z) eq?)) ;; 0.412726 real 0.412026 user 0.000000 sys #<unspecified>

 トレースを実現した例

(define (for-each/trace proc ls :optional (trace? #f))
  (with-special-cased-conditional (trace? trace?)
    (let lp ((ls ls) (i 0))
      (if (and (trace?) (zero? (modulo i 100)))
          (print "iteration: " i))
      (cond
       ((pair? ls)
        (proc (car ls))
        (lp (cdr ls) (+ i 1)))))))

;;; with-special-cased-conditionalの展開 (if trace? (let-syntax ((trace? (lambda (x) (syntax-case x () ((dummy) (syntax #t)))))) (let lp ((ls ls) (i 0)) (if (and (trace?) (zero? (modulo i 100))) (print "iteration: " i)) (cond ((pair? ls) (proc (car ls)) (lp (cdr ls) (+ i 1)))))) (let-syntax ((trace? (lambda (x) (syntax-case x () ((dummy) (syntax #f)))))) (let lp ((ls ls) (i 0)) (if (and (trace?) (zero? (modulo i 100))) (print "iteration: " i)) (cond ((pair? ls) (proc (car ls)) (lp (cdr ls) (+ i 1)))))))

(for-each/trace (lambda (x) (display ".") (when (zero? (modulo x 50)) (newline))) (iota 500 1) #t)

sash> iteration: 0 .................................................. .................................................. iteration: 100 .................................................. .................................................. iteration: 200 .................................................. .................................................. iteration: 300 .................................................. .................................................. iteration: 400 .................................................. .................................................. iteration: 500 #<unspecified>

という風にマクロ展開により、オプションによるオーバーヘッドはほぼありません。

まとめ

 今回は、special-caseを紹介してみました。
最適化やフックの実現でディスパッチのオーバーヘッドを減らしたい場合などに使えそうなニッチなマクロですね。

comments powered by Disqus