File Information
File: 05-lr/acl_arc_1_sum/cleansed_text/xml_by_section/abstr/91/j91-1004_abstr.xml
Size: 5,777 bytes
Last Modified: 2025-10-06 13:47:10
<?xml version="1.0" standalone="yes"?> <Paper uid="J91-1004"> <Title>Technical Correspondence Techniques for Automatic Memoization with Applications to Context-Free Parsing</Title> <Section position="2" start_page="0" end_page="92" type="abstr"> <SectionTitle> 1. Memoization </SectionTitle> <Paragraph position="0"> The term memoization was coined by Donald Michie (1968) to refer to the process by which a function is made to automatically remember the results of previous computations. The idea has become more popular in recent years with the rise of functional languages; Field and Harrison (1988) devote a whole chapter to it. The basic idea is just to keep a table of previously computed input/result pairs. In Common Lisp one could write: 1 (setf (gethash x table) (funcall fn x))))))).</Paragraph> <Paragraph position="1"> (For those familiar with Lisp but not Common Lisp, gethash returns two values, the stored entry in the table, and a boolean flag indicating if there is in fact an entry. The special form multiple-value-bind binds these two values to the symbols val and found. The special form serf is used here to update the table entry for x.) In this simple implementation fn is required to take one argument and return one value, and arguments that are eql produce the same value. Below we will relax some of these restrictions. The problem with this approach is that we also want to memoize any recursive calls that fn may make. To use the canonical example, if we have: * Computer Science Division, University of California, Berkeley, CA 94720 1 All examples are in Common Lisp, rather than in generic pseudo-code for two reasons. First, I want to stress that these techniques can actually be used in existing languages. Second, Common Lisp provides a rich set of primitives (such as make-hash-table) that would otherwise require lengthy explanations. then the function (memo (function fib)) will not have linear-order complexity, because recursive calls will go to the original function fib, not to the memoized version. One way to fix this problem is to assign the new memoized function to the name f lb. Common Lisp is a good host language for this approach, because there are primitives for accessing and altering the global function name space. Consider the following: (defun memoize (fn-name) &quot;Replace fn-name's global definition with a memoized version.&quot; (setf (symbol-functio n fn-name) (memo (symbol-function fn-name)))).</Paragraph> <Paragraph position="2"> When passed a symbol that names a function, memoize changes the global definition of the function to a memo-function. Thus, any recursive calls will go first to the memo-function, rather than to the original function. This is just what we want; all we have to say is (memoize ' fib) to transform fib from an exponential- to a linear-order algorithm.</Paragraph> <Paragraph position="3"> To make sure that the memoization step isn't left out during program development, some programmers may prefer to write code like this: (memoize (defun f (x) ...)).</Paragraph> <Paragraph position="4"> Or even like this: (defmacro defun-memo (fn args &body body) &quot;Define a memoized function.&quot; ~(memoize (defun ,fn ,args . ,body))) (defun-memo f (x) ...).</Paragraph> <Paragraph position="5"> Both of these approaches rely on the fact that defun returns the name of the function defined. Note that the following will not work</Paragraph> <Paragraph position="7"> because memoize affects only the global function binding, not the lexical (local) definition. null The version of memo presented above suffers from a serious limitation -- the function to be memoized can only have one argument. In the revised definition given below, the function can take any number of arguments, and the indexing can be on</Paragraph> <Section position="1" start_page="92" end_page="92" type="sub_section"> <SectionTitle> Norvig Memoization and Context-Free Parsing </SectionTitle> <Paragraph position="0"> any function of the arguments. When there is only argument, the default key function, first, is appropriate. To hash on all the arguments, use identity as the key.</Paragraph> <Paragraph position="1"> (defun memo (fn &key (key #'first) (test #'eql) name) &quot;Return a memo-function of fn.&quot; (let ((table (make-hash-table :test test))) (serf (get name 'memo) table) #'(lambda (&rest args) (let ((k (funcall key args))) (multiple-value-bind (val found) (gethash k table) (if found val (setf (gethash k table) (apply fn args)))))))) (defun memoize (fn-name &key (key #'first) (test #'eql)) &quot;Replace fn-name's global definition with a memoized version.&quot; (setf (symbol-function fn-name) (memo (symbol-function fn-name) :name fn-name :key key :test test))) (defun clear-memoize (fn-name) (clrhash (get fn-name ~memo))) Also note that the hash table is stored away on the function name's property list, so that it can be inspected or cleared at will. The intent is that the user's program should clear the table when the results are likely to be out of the working set. We might also want a hash mechanism that caches only recent entries, and discards earlier ones, as originally suggested by Michie (1968).</Paragraph> <Paragraph position="2"> The user also has the responsibility of choosing the appropriate hashing function. By choosing eql instead of equal hashing, for example, hashing overhead will be reduced, but computation will be duplicated for equal lists. A compromise is to use eql hashing in conjunction with unique or canonical lists (Szolovits and Martin, 1981). eql hashing has the additional advantage of allowing infinite circular lists, as discussed by Hughes (1985).</Paragraph> </Section> </Section> class="xml-element"></Paper>