emacs-memoize-1.1/0000755000175000017500000000000013134251236013740 5ustar dogslegdogslegemacs-memoize-1.1/memoize-test.el0000644000175000017500000000370513134251236016711 0ustar dogslegdogsleg;;; Tests for memoize.el -*- lexical-binding: t; -*- (require 'ert) (require 'cl-lib) (require 'memoize) (defvar memoize-numcalls 0) (defun memoize-testfunc1 (_) (cl-incf memoize-numcalls)) (ert-deftest memoize () ;; Have to defun each time since we don't want to keep re-memoizing ;; the same function. (setq memoize-numcalls 0) (let ((run-at-time-timeout) (run-at-time-func) (timer-canceled)) (cl-letf (((symbol-function 'run-at-time) (lambda (timeout repeat func) (setq run-at-time-timeout timeout) (should (null repeat)) (setq run-at-time-func func))) ((symbol-function 'cancel-timer) (lambda (_) (setq timer-canceled t)))) (memoize #'memoize-testfunc1 "10 seconds") (should (eq 0 memoize-numcalls)) (memoize-testfunc1 1) (should run-at-time-func) (should (eq 1 memoize-numcalls)) ;; Timer should be called now (should (equal "10 seconds" run-at-time-timeout)) (memoize-testfunc1 1) ;; This should be cached (should (eq 1 memoize-numcalls)) (funcall run-at-time-func) ;; Now the cache should be gone (memoize-testfunc1 1) (message "Finished running memoize-testfunc1") (should (eq 2 memoize-numcalls)) ;; Another arg is another call (memoize-testfunc1 2) (should (eq 3 memoize-numcalls)) (should timer-canceled)))) (defun memoize-testfunc2 (_a _b) (cl-incf memoize-numcalls)) (ert-deftest memoize-by-buffer-contents () (let ((f (memoize-by-buffer-contents--wrap #'memoize-testfunc2))) (setq memoize-numcalls 0) (with-temp-buffer (funcall f 0 0) (should (eq 1 memoize-numcalls)) (funcall f 0 1) (should (eq 2 memoize-numcalls)) (funcall f 0 0) (should (eq 2 memoize-numcalls)) (insert "hello world") (funcall f 0 0) (should (eq 3 memoize-numcalls))))) emacs-memoize-1.1/Makefile0000644000175000017500000000053313134251236015401 0ustar dogslegdogsleg.POSIX: .SUFFIXES: .el .elc EMACS = emacs test: memoize.elc memoize-test.elc $(EMACS) -Q -batch -L . -l memoize-test.el -f ert-run-tests-batch compile: memoize.elc memoize-test.elc clean: rm -f memoize.elc memoize-test.elc memoize.elc: memoize.el memoize-test.elc: memoize-test.el .el.elc: $(EMACS) -Q -batch -L . -f batch-byte-compile $< emacs-memoize-1.1/README.md0000644000175000017500000000112313134251236015214 0ustar dogslegdogsleg# Elisp memoization functions See the header in the source file for details. It's very easy to use: ```cl (require 'memoize) (memoize 'my-function) ``` The macro `defmemoize` is also provided to directly create memoized functions: ```cl (defmemoize my-expensive-function (x) (if (zerop n) 1 (* n (my-expensive-function (1- n))))) ``` Some functions are run over buffer contents, and need to be cached only so long as the buffer contents do not change. For these use-cases, we have the function `memoize-by-buffer-contents` as well as the `defmemoize-by-buffer-contents` macro. emacs-memoize-1.1/UNLICENSE0000644000175000017500000000227313134251236015214 0ustar dogslegdogslegThis is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to emacs-memoize-1.1/memoize.el0000644000175000017500000001577513134251236015746 0ustar dogslegdogsleg;;; memoize.el --- Memoization functions -*- lexical-binding: t; -*- ;; This is free and unencumbered software released into the public domain. ;; Author: Christopher Wellons ;; URL: https://github.com/skeeto/emacs-memoize ;; Version: 1.1 ;;; Commentary: ;; `memoize' accepts a symbol or a function. When given a symbol, the ;; symbol's function definition is memoized and installed overtop of ;; the original function definition. When given a function, it returns ;; a memoized version of that function. ;; (memoize 'my-expensive-function) ;; `defmemoize' defines a memoized function directly, behaving just ;; like `defun'. ;; (defmemoize my-expensive-function (x) ;; (if (zerop n) ;; 1 ;; (* n (my-expensive-function (1- n))))) ;; Memoizing an interactive function will render that function ;; non-interactive. It would be easy to fix this problem when it comes ;; to non-byte-compiled functions, but recovering the interactive ;; definition from a byte-compiled function is more complex than I ;; care to deal with. Besides, interactive functions are always used ;; for their side effects anyway. ;; There's no way to memoize nil returns, but why would your expensive ;; functions do all that work just to return nil? :-) ;; Memoization takes up memory, which should be freed at some point. ;; Because of this, all memoization has a timeout from when the last ;; access was. The default timeout is set by ;; `memoize-default-timeout'. It can be overriden by using the ;; `memoize' function, but the `defmemoize' macro will always just use ;; the default timeout. ;; If you wait to byte-compile the function until *after* it is ;; memoized then the function and memoization wrapper both get ;; compiled at once, so there's no special reason to do them ;; separately. But there really isn't much advantage to compiling the ;; memoization wrapper anyway. ;;; Code: (require 'cl-lib) (defvar memoize-default-timeout "2 hours" "The amount of time after which to remove a memoization. This represents the time after last use of the memoization after which the value is expired. Setting this to nil means to never expire, which will cause a memory leak, but may be acceptable for very careful uses.") (defun memoize (func &optional timeout) "Memoize FUNC: a closure, lambda, or symbol. If argument is a symbol then install the memoized function over the original function. The TIMEOUT value, a timeout string as used by `run-at-time' will determine when the value expires, and will apply after the last access (unless another access happens)." (cl-typecase func (symbol (put func 'function-documentation (concat (documentation func) " (memoized)")) (fset func (memoize--wrap (symbol-function func) timeout)) func) (function (memoize--wrap func timeout)))) (defun memoize--wrap (func timeout) "Return the memoized version of FUNC. TIMEOUT specifies how long the values last from last access. A nil timeout will cause the values to never expire, which will cause a memory leak as memoize is use, so use the nil value with care." (let ((table (make-hash-table :test 'equal)) (timeouts (make-hash-table :test 'equal))) (lambda (&rest args) (let ((value (gethash args table))) (unwind-protect (or value (puthash args (apply func args) table)) (let ((existing-timer (gethash args timeouts)) (timeout-to-use (or timeout memoize-default-timeout))) (when existing-timer (cancel-timer existing-timer)) (when timeout-to-use (puthash args (run-at-time timeout-to-use nil (lambda () (remhash args table))) timeouts)))))))) (defmacro defmemoize (name arglist &rest body) "Create a memoize'd function. NAME, ARGLIST, DOCSTRING and BODY have the same meaning as in `defun'." (declare (indent defun)) `(progn (defun ,name ,arglist ,@body) (memoize (quote ,name)))) (defun memoize-by-buffer-contents (func) "Memoize the given function by buffer contents. If argument is a symbol then install the memoized function over the original function." (cl-typecase func (symbol (put func 'function-documentation (concat (documentation func) " (memoized by buffer contents)")) (fset func (memoize-by-buffer-contents--wrap (symbol-function func))) func) (function (memoize-by-buffer-contents--wrap func)))) (defun memoize-by-buffer-contents--wrap (func) "Return the memoization based on the buffer contents of FUNC. This form of memoization will be based off the current buffer contents. A different memoization is stored for all buffer contents, although old contents and no-longer-existant buffers will get garbage collected." ;; We need 3 tables here to properly garbage collect. First is the ;; table for the memoization itself, `memoization-table'. It holds a ;; cons of the content hash and the function arguments. ;; ;; Buffer contents change often, though, so we want these entries to ;; be automatically garbage collected when the buffer changes or the ;; buffer goes away. To keep the entries around, we need to tie the ;; content hash to the buffer, so that the content hash string ;; doesn't go away until the buffer does. We do that with the ;; `buffer-to-contents-table'. ;; ;; But even if the buffer content does change, we need to expire the ;; memoization entries for that particular buffer content. So we ;; have a `contents-to-memoization-table' that we use to tie the ;; content hash to the memoization conses used as keys in the ;; `memoization-table'. ;; ;; If a buffer's value changes, we make sure the next time we put a ;; new value at the `buffer-to-contents-table', which causes the ;; hash string to disappear. This causes the hash-string to ;; disappear from the `contents-to-memoization-table', which causes ;; the memoizations based on that content string to disappear from ;; the `memoization-table'. (let ((memoization-table (make-hash-table :test 'equal :weakness 'key)) (buffer-to-contents-table (make-hash-table :weakness 'key)) (contents-to-memoization-table (make-hash-table :weakness 'key))) (lambda (&rest args) (let* ((bufhash (secure-hash 'md5 (buffer-string))) (memokey (cons bufhash args)) (value (gethash memokey memoization-table))) (or value (progn (puthash (current-buffer) bufhash buffer-to-contents-table) (puthash bufhash memokey contents-to-memoization-table) (puthash memokey (apply func args) memoization-table))))))) (defmacro defmemoize-by-buffer-contents (name arglist &rest body) "Create a memoize'd-by-buffer-contents function. NAME, ARGLIST, DOCSTRING and BODY have the same meaning as in `defun'." (declare (indent defun)) `(progn (defun ,name ,arglist ,@body) (memoize-by-buffer-contents (quote ,name)))) (provide 'memoize) ;;; memoize.el ends here