File: memoize.lisp

package info (click to toggle)
mcvs 1.0.13-8
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 668 kB
  • ctags: 648
  • sloc: lisp: 5,091; ansic: 223; sh: 190; makefile: 58
file content (177 lines) | stat: -rw-r--r-- 8,028 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
;;; This source file is part of the Meta-CVS program, 
;;; which is distributed under the GNU license.
;;; Copyright 2002 Kaz Kylheku

(require "multi-hash")
(provide "memoize")

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defun remove-key-aux-rest (lambda-list)
"Remove any trailing portion of the lambda list that starts with
one of the lambda list keywords &rest, &key, &aux or &body."
    (cond
      ((null lambda-list) nil)
      ((member (first lambda-list) '(&rest &aux &key &body)) nil)
      (t (cons (first lambda-list) (remove-key-aux-rest (rest lambda-list))))))

  (defun strip-lambda-list (list)
"Simplify a lambda list by removing occurences of &optional, stripping
away the trailing portion using REMOVE-KEY-AUX-REST, canonicalizing
simple variable forms to lists so that SYMBOL becomes (SYMBOL), and removing
initializers so that (SYMBOL INITFORM) becomes (SYMBOL). This stripped
lambda list becomes the lambda list of the hidden inner function that
implements the guts of a memoized function, and captures the recursive
calls. The &OPTIONAL parameters are reduced to required ones, and there
are no trailing &KEY or &rest parameters."
    (labels ((simplify-var-form (form)
	       (cond
		 ((eq '&optional form) nil)
		 ((symbolp form) (list form))
		 ((consp form) (list (first form)))
		 (t (error "MEMOIZE-EXPANDER: bad thing in lambda list: ~a~%"
			   form)))))
      (mapcan #'simplify-var-form (remove-key-aux-rest list))))

  (defun extract-tests (lambda-list)
"The memoize module understands special lambda lists which, as an extension
to regular Lisp lambda lists, allow the programmer to specify what test
function should be used for the hash table used to memoize over a given
parameter. This function parses such a lambda list and extracts the tests.
The subtlety here is that after the &optional keyword, the syntax changes.
For required parameters, the syntax which specifies the test is
(SYMBOL :TEST FUNCTION). For &optional paramters, the syntax becomes
(SYMBOL INITFORM :TEST FUNCTION). For any variable which doesn't specify
a test, the test is assumed to be #'eql."
    (let ((saw-optional))
      (mapcan #'(lambda (element)
		  (cond
		    ((eq '&optional element)
		       (setf saw-optional t)
		       nil)
		    ((consp element)
		       (if saw-optional
			 (destructuring-bind (var &optional init &key test) element
			    (declare (ignore var init))
			    (list (or test '#'eql)))
			 (destructuring-bind (var &key test) element
			    (declare (ignore var init))
			    (list (or test '#'eql)))))
		    (t (list '#'eql))))
	      (remove-key-aux-rest lambda-list))))

  (defun remove-tests (lambda-list)
"This function removes the :test specifications from a the memoized
function lambda list, thereby reducing it to a regular lambda list.
See the docstring for EXTRACT-TESTS for a little more information.
We need to do this to generate the outer shell of the memoized function,
which is a normal Lisp function."
    (let (saw-optional saw-key)
      (mapcar #'(lambda (element)
		  (cond
		    ((eq '&optional element)
		       (setf saw-optional t)
		       element)
		    ((eq '&key element)
		       (setf saw-key t)
		       element)
		    ((consp element)
		       (if saw-key
			 element
			 (if saw-optional
			   (destructuring-bind (var &optional init &key test) element
			      (declare (ignore test))
			      (append (list var) (if init (list init))))
			   (destructuring-bind (var &key test) element
			      (declare (ignore test))
			      var))))
		    (t element)))
	      lambda-list)))

(defun memoize-expander (name lambda-list tests body expr)
"Produce a memoized function in the form of a LABELS function that is
wrapped in a LET block. The LET block sets up the hash table, either
a regular hash table if there is one paramter, or a MULTI-HASH if
there are several parameters.  The body of the LABELS performs the 
memoization stuff with the hash tables. Note that the function
FACTOR-MEMO-LABELS depends on this structure of the LABELS nested
within the LET."
  (let ((multi-hash-sym (gensym "MULTI-HASH-"))
	(hash-result-sym (gensym "HASH-RESULT-"))
	(hash-found-sym (gensym "HASH-FOUND-"))
	(dimensions (length lambda-list)))
    `(let ((,multi-hash-sym ,(if (> dimensions 1)
			       `(make-instance 'multi-hash
					       :dimensions ,dimensions
					       :tests ,tests)
			       `(make-hash-table :test ,(second tests)))))
       (labels ((,name ,lambda-list
		  (multiple-value-bind (,hash-result-sym ,hash-found-sym)
				       ,(if (> dimensions 1)
					  `(get-multi-hash ,multi-hash-sym
							   ,@lambda-list)
					  `(gethash ,@lambda-list 
						    ,multi-hash-sym))
		    (if ,hash-found-sym
		      ,hash-result-sym
		      (setf ,(if (> dimensions 1)
			       `(get-multi-hash ,multi-hash-sym ,@lambda-list)
			       `(gethash ,@lambda-list ,multi-hash-sym))
			    (progn ,@body)))))) ,expr))))

(defun factor-memo-labels (memo-labels forms)
"This function takes a list of the LET expressions, each of which is assumed to
be generated by MEMOIZE-EXPANDER, and factors them out to produce one giant
LET block with all of the LET material (hash tables) coalesced together,
enclosing one big coalesced LABELS block that defines all of the functions
together. This trick allows us to generate individual memoized inner functions
using MEMOIZE-EXPANDER, and then fuse them together to make one big
party of mutually recursive memoized functions."
  (macrolet ((destructure-memo-labels (labels &body labels-forms)
	       `(destructuring-bind (name outer-lambda 
				      (let inner-lambda &rest labels-forms)) 
				    ,labels 
		  (declare (ignorable name outer-lambda let inner-lambda
				      labels-forms))
		  ,@labels-forms)))
    (flet ((extract-lets (labels)
	     (destructure-memo-labels labels
	       inner-lambda))
	   (extract-funcs (labels)
	     (destructure-memo-labels labels
	       `(,name ,outer-lambda ,@labels-forms))))
      `(let ,(mapcan #'extract-lets memo-labels)
	 (labels ,(mapcar #'extract-funcs memo-labels) ,@forms))))))

(defmacro define-memoized-function (name lambda-list &body forms)
"Generate a DEFUN definition for a function called NAME, placing the
body into an inner recursive function of the same name that is memoized.
Effectively, this creates a memoized function: one whose recursive calls
are automatically cached using the parameter lists as keys into a multi-level
hash table. This is an important optimization technique when the recursion
contains overlapping cases; it can reduce exponential time to polynomial time.
This macro understands a special lambda list syntax. A required parameter
normally written as SYMBOL can be written (SYMBOL :TEST FUNC) to specify a
hashing equality function FUNC for that parameter which can be #'EQ,
#'EQL, #'EQUAL or #'EQUALP. For an optional parameter, this syntax is
(SYMBOL INIT-FORM :TEST FUNC). Note that only the outer function accepts
&KEY and &REST parameters, if any are specified. The inner recursive memoized
function does not; it has a simplified lambda list."
  (let ((stripped-ll (strip-lambda-list lambda-list)))
    `(defun ,name ,(remove-tests lambda-list)
       ,(memoize-expander name stripped-ll `(list ,@(extract-tests lambda-list))
			  forms `(,name ,@stripped-ll)))))

(defmacro memoized-labels ((&rest memoized-labels-list) &body forms)
"Generate a block of mutually recursive LABELS functions, making the
DEFINE-MEMOIZED-FUNCTION utility available for local functions. See
the documentation string for that macro for more details."
  (flet ((generate-labels-element (labels)
	   (destructuring-bind (name lambda-list &body labels-forms) labels
	     (let ((stripped-ll (strip-lambda-list lambda-list)))
	       `(,name ,(remove-tests lambda-list)
		   ,(memoize-expander name stripped-ll 
				      `(list ,@(extract-tests lambda-list))
				      labels-forms 
				      `(,name ,@stripped-ll)))))))
  (factor-memo-labels (mapcar #'generate-labels-element 
			      memoized-labels-list) forms)))