#lang scheme
(provide define/memo memo-lambda define/memo* memo-lambda*)
(define undefined (letrec ([x x]) x))
(define (assoc/inner-eq arglist cache)
(cond
[(null? cache) #f]
[(let loop ([left arglist]
[right (caar cache)])
(cond
[(and (null? left) (null? right)) #t]
[(null? left) #f]
[(null? right) #f]
[else (and (eq? (car left) (car right))
(loop (cdr left) (cdr right)))]))
(car cache)]
[else (assoc/inner-eq arglist (cdr cache))]))
(define-for-syntax (lambda-or-define-args lambda-or-define args)
(cond
[(free-identifier=? lambda-or-define #'lambda) args]
[(free-identifier=? lambda-or-define #'define)
(with-syntax ([(name . rest) args])
#'rest)]
[else (raise-syntax-error 'memoize "internal error" lambda-or-define)]))
(define-syntax local-declaration
(syntax-rules (lambda define)
[(_ lambda ([var binding] ...) body)
(let ([var binding] ...) body)]
[(_ define ([var binding] ...) body)
(begin (define var binding) ... body)]))
(define-syntax (memo-lambda-or-define/memo stx)
(syntax-case stx ()
[(_ lambda-or-define build-hash-table hash-code assoc args body0 body1 ...)
(syntax-case (lambda-or-define-args #'lambda-or-define #'args) ()
[()
#'(local-declaration lambda-or-define ([cached? #f] [cache undefined])
(lambda-or-define args
(unless cached?
(set! cache (let () body0 body1 ...))
(set! cached? #t))
cache))]
[(arg)
#'(local-declaration lambda-or-define ([cache build-hash-table])
(lambda-or-define args
(hash-ref cache arg (lambda ()
(let ([ans (let () body0 body1 ...)])
(hash-set! cache arg ans)
ans)))))]
[multiple-args
(let loop ([arg-exp #'multiple-args] [fixed-args null])
(syntax-case arg-exp ()
[(arg . rest)
(loop #'rest (cons #'arg fixed-args))]
[rest
(with-syntax ([list-exp (let loop ([fixed-args fixed-args]
[result (syntax-case #'rest ()
[() #''()] [_ #'rest])])
(if (null? fixed-args)
result
(loop (cdr fixed-args)
#`(cons #,(car fixed-args) #,result))))])
#'(local-declaration lambda-or-define ([cache (make-hasheq)])
(lambda-or-define args
(let* ([arg-list list-exp]
[key (apply bitwise-xor (map hash-code arg-list))]
[alist (hash-ref cache key (lambda () null))])
(cond
[(assoc arg-list alist) => cdr]
[else (let ([ans (let () body0 body1 ...)])
(hash-set! cache key (cons (cons arg-list ans) alist))
ans)])))))]))])]))
(define-syntax memo-lambda
(syntax-rules ()
[(_ args body0 body1 ...)
(memo-lambda-or-define/memo lambda (make-hasheq) eq-hash-code assoc/inner-eq args body0 body1 ...)]))
(define-syntax memo-lambda*
(syntax-rules ()
[(_ args body0 body1 ...)
(memo-lambda-or-define/memo lambda (make-hash) equal-hash-code assoc args body0 body1 ...)]))
(define-syntax define/memo
(syntax-rules ()
[(_ args body0 body1 ...)
(memo-lambda-or-define/memo define (make-hasheq) eq-hash-code assoc/inner-eq args body0 body1 ...)]))
(define-syntax define/memo*
(syntax-rules ()
[(_ args body0 body1 ...)
(memo-lambda-or-define/memo define (make-hash) equal-hash-code assoc args body0 body1 ...)]))