メモ化マクロを作ってみた(tarai)

e.g.

;;メモ化マクロ(define-memoize)を利用
(define-memoize (tarai (x 'eqv?) (y 'eqv?) (z 'eqv?))
  (if (<= x y)
      y
      (tarai (tarai (1- x) y z) (tarai (1- y) z x) (tarai (1- z) x y))))

;;普通の定義
(define (tarai2 x y z)
  (if (<= x y)
      y
    (tarai2 (tarai2 (1- x) y z) (tarai2 (1- y) z x) (tarai2 (1- z) x y))))

(define (1- x) (- x 1))


(let ((*x* #f) (*y* #f))
  (time (set! *x* (tarai 12 7 0)))
  (time (set! *y* (tarai2 12 7 0)))
  (print (equal? *x* *y*)))

;; ;(time (set! *x* (tarai 12 7 0)))
;; ; real   0.001
;; ; user   0.000
;; ; sys    0.000
;; ;(time (set! *y* (tarai2 12 7 0)))
;; ; real   1.810
;; ; user   1.720
;; ; sys    0.000
;; #t

code

(use util.match)

(define-macro (define-memoize . args)
  ;;(define-memoize (name ((arg cmpf) ...)
  ;;                       body ...)
  (define (memo-get-recursive ht calc keys eqs)
    (let loop ((ht ht) (args (map list keys eqs)))
      (match args
	[((key #f))
	 `(or (hash-table-get ,ht ,key #f)
	      (rlet1 ans (,calc ,@keys)
		(hash-table-put! ,ht ,key ans)))]
	[((key eq) . rest)
	 (let1 next-table (gensym)
	   `(let1 ,next-table (or (hash-table-get ,ht ,key #f)
				  (make-hash-table ,eq))
	      (rlet1 ans ,(loop next-table rest)
		(hash-table-put! ,ht ,key ,next-table))))])))
  (match args
    [((name . head) . body)
     (let ((keys (map car head))
	   (cmpfs (map cadr head))
	   (ht (gensym)) (calc (gensym)))
       `(define ,name
	  (let1 ,ht (make-hash-table ,(car cmpfs))
	    (define (,calc ,@keys)
	      ,@body)
	    (lambda ,keys
	      ,(memo-get-recursive ht calc keys `(,@(cdr cmpfs) #f))))))]))

n引数の手続きの場合、どちらの方がいいんだろう?

  • 1つのhash-tableに対してkeyをリストにしてequal?で比較してメモ化
  • hash-tableをネストさせていく
    • (get (get (get ht k0) k1) k2)というような。
;;keyをlistにしてequal?で比較
(define tarai3
  (let1 ht (make-hash-table 'equal?)
    (define (calc x y z)
      (if (<= x y)
	  y
	  (tarai3 (tarai3 (1- x) y z) (tarai3 (1- y) z x) (tarai3 (1- z) x y))))
    (lambda (x y z)
      (let1 key (list x y z)
	(or (hash-table-get ht key #f)
	    (rlet1 r (calc x y z)
		(hash-table-put! ht key r)))))))

;; ;(time (set! *x* (tarai 200 150 0)))
;; ; real   0.183
;; ; user   0.160
;; ; sys    0.020
;; ;(time (set! *z* (tarai3 200 150 0)))
;; ; real   0.193
;; ; user   0.180
;; ; sys    0.000

equal?の速度が遅くならないようなものなら、直接listをKeyにして比較しても良さそう。
頑張ったのは徒労だったかもしれない。