A robust caching and memoization framework for Emacs Lisp with TTL, persistence, and async support.
Cacheus is a powerful, flexible, and feature-rich caching framework designed to bring modern caching capabilities to Emacs Lisp. It provides developers with an easy-to-use API to speed up slow functions, reduce redundant computations, and manage expensive resources like API calls.
Whether you're looking to memoize a computationally intensive function or build a persistent, TTL-aware cache for network requests, Cacheus provides the tools you need in a single, cohesive package.
- Multiple Caching Strategies:
- Function Memoization: Transparently cache the results of any function using
cacheus-memoize!. - Generic Caching: Manually
getandputkey-value pairs usingcacheus-cache!. - Runtime Memoization: Wrap existing functions at runtime with
cacheus-memoize-fn.
- Function Memoization: Transparently cache the results of any function using
- Time-To-Live (TTL): Automatically expire cache entries after a set duration.
- Capacity Management: Limit the number of items in a cache with configurable eviction strategies (
:lru,:lfu,:fifo). - Persistence: Automatically save caches to a file and load them on startup.
- Asynchronous Computation: Prevent UI blocking by running expensive computations in the background on cache misses.
- Cache Invalidation:
- Versioning: Invalidate all entries by bumping a
:versionnumber. - Tagging: Invalidate groups of entries by tags using
cacheus-invalidate-by-tags!.
- Versioning: Invalidate all entries by bumping a
- Global Management: A full suite of interactive functions (
cacheus-list-all-caches,cacheus-clear-all-caches, etc.) to inspect and manage all defined caches.
The recommended way to install Cacheus is with use-package:
(use-package cacheus
:ensure t)Cacheus provides three main primitives for creating caches.
Use cacheus-memoize! to define a new function that has caching built-in from the start. It's perfect for your own helper functions that are computationally expensive.
;; Imagine this function takes a long time to run
(defun my-slow-computation (x)
(message "Computing for %S..." x)
(sleep-for 2)
(* x x))
;; Create a memoized version with a 10-minute (600s) TTL
(cacheus-memoize! my-cached-computation (x)
:ttl 600
(my-slow-computation x))
;;; Usage
(my-cached-computation 5) ;; => First call: "Computing for 5...", waits 2s, returns 25
(my-cached-computation 5) ;; => Second call: returns 25 instantlyUse cacheus-cache! to create a generic key-value cache. This gives you full control to get and put data manually. It's ideal for caching data from external sources like APIs.
(require 'url-json)
(cacheus-cache! api-cache
:ttl 3600 ; Cache results for 1 hour
:capacity 100 ; Store up to 100 results
:cache-file "~/.emacs.d/cache/api-cache.el") ; Persist to a file
(defun get-user-data (username)
"Fetch user data from the network or return from cache."
(or (cacheus-get! 'api-cache username)
(let* ((url (format "[https://api.github.com/users/%s](https://api.github.com/users/%s)" username))
(response (url-retrieve-synchronously url))
(data (json-read-from-string (url-contents response))))
;; Put the result in the cache for next time
(cacheus-put! 'api-cache username data))))Use cacheus-memoize-fn to add caching to an existing function at runtime, without modifying its original definition. This is useful for optimizing functions from other libraries.
;; Suppose `some-library-parse` is a slow function from another package.
(defun some-library-parse (text)
(sleep-for 1)
(length text))
;; Create a new, memoized version of it at runtime.
(defvar my-cached-parser
(cacheus-memoize-fn #'some-library-parse
:arglist '(text) ;; Must provide the original arglist
:ttl 1800))
;;; Usage
(funcall my-cached-parser "hello") ;; => First call: waits 1s, returns 5
(funcall my-cached-parser "hello") ;; => Second call: returns 5 instantlyWhile wrapper functions are convenient, you can also interact with any cache directly using the cacheus-get!, cacheus-put!, and cacheus-clear! macros.
Retrieve a value by its key. For memoized functions, you pass the arguments that form the key.
;; For a generic cache
(cacheus-get! 'api-cache "tarsius") ;; => Returns the cached data or nil
;; For a memoized function
(cacheus-get! 'my-cached-computation 5) ;; => Returns 25 if cached, or computes and caches itManually insert or update a value in a cache.
;; For a generic cache
(cacheus-put! 'api-cache "new-user" '(:name "New" :id 123))
;; For a memoized function
(cacheus-put! 'my-cached-computation 10 100) ;; Manually sets the cache for input `10` to `100`Clear all entries from a specific cache.
(cacheus-clear! 'api-cache) ;; Removes all entries from the api-cache
(cacheus-clear! 'my-cached-computation) ;; Removes all entries from the memoized function cacheYou can customize cache behavior with a rich set of options.
| Option | Description | Example |
|---|---|---|
:ttl |
Time-To-Live (seconds). The duration for which a cache entry is considered fresh. | :ttl 3600 |
:refresh-ttl |
If t, the TTL countdown for an entry resets on every access (a "sliding" expiration). |
:refresh-ttl t |
:capacity |
The maximum number of items to store in the cache. | :capacity 200 |
:eviction-strategy |
Algorithm to use when at capacity. Options: :lru (default), :lfu, :fifo. |
:eviction-strategy :lfu |
:cache-file |
Path to a file for persistence. The cache is automatically loaded on startup and saved on shutdown. | :cache-file "~/.emacs.d/cache/my-cache" |
:version |
A value (string, number, etc.) used for bulk invalidation. Change the version to treat all old entries as stale. | :version "my-app-v1.1" |
:async |
If t, the computation on a cache miss runs asynchronously and returns a promise (concur future). |
:async t |
:predicate |
A function (lambda (value)) that must return non-nil for a result to be cached. |
:predicate 'identity (caches non-nil) |
:tags-fn |
A function (lambda (key value)) that returns a list of symbols to tag the entry with for invalidation. |
:tags-fn (lambda (k v) (list 'users k)) |
:key-fn |
(Memoize only) A function to transform arguments into a canonical cache key. | :key-fn (lambda (a b) (list a)) |
Cacheus provides interactive commands to manage all your caches globally.
M-x cacheus-list-all-caches: Open a buffer listing all registered caches. From here, you can inspect, clear, or save individual caches.M-x cacheus-clear-all-caches: Clear all entries from all caches. With a prefix argument, you can filter by name.M-x cacheus-save-all-caches: Persist all file-backed caches to disk.M-x cacheus-load-all-caches: Load all file-backed caches from disk.
Contributions are welcome! If you find a bug or have a feature request, please open an issue on GitHub. If you'd like to contribute code, please open a pull request.
This project is licensed under the MIT License. See the LICENSE file for details.