File size: 4,238 Bytes
43203b4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
(in-package :clache)
(use-syntax :annot)

#|

API
---

### Caches

A cache is a triple of a key, a value, and an expiration time.

### Cache Keys

Any object can be used as a cache key if the object can be converted
into a string properly by using CACHE-KEY-TO-STRING.

### Cache Values

Same as cache keys, any object can be used as a cache value. However,
a type of cache values can be limited by stores. So you have to be
careful what store are you using.

### Expiration Time

An expiration time describes how long caches live in seconds. If an
expiration time is NIL, such caches will never be expired: persistent
cache.

### Cache Existence

If a cache is stored in a store and has not yet been expired or a
persitent cache, we express the cache exists in the store.

### Stores

Store is an abstract layer of maintaining caches. You can access
stores via API.

|#

@export
(defun getcache (key store)
  "Retrieve a cache value from STORE indicated by KEY and return
values of the cache value and a boolean whether the cache exists in
STORE. The cache value will be NIL if such the cache doesn't
exist. For example, (getcache \"not-existed-cache\") will return NIL,
NIL."
  ;@type (store store)
  (load-cache key store))

@export
(defun setcache (key value store &key expire)
  "Store a cache VALUE into STORE with KEY and EXPIRE. EXPIRE is an
expiration time in seconds. If EXPIRE is NIL, the cache will never be
expired. The return value is VALUE that has been stored."
  ;@type (store store)
  ;@type (expire expire)
  (store-cache key value store expire))

@export
(defun (setf getcache) (value key store &key expire)
  ;@type (store store)
  ;@type (expire expire)
  (store-cache key value store expire))

@export
(defun remcache (key store)
  "Remove a cache from STORE indicated by KEY. If the cache has been
successfully removed, this function returns T, otherwise returns NIL."
  ;@type (store store)
  (delete-cache key store))

@export
(defun clrcache (store)
  "Remove all caches from STORE. The return value is undefined."
  ;@type (store store)
  (clear-cache store))

@export
(defmacro with-cache ((key &key store expire) &body body)
  "If a cache indicated by KEY exists, this just returns the cache
value without evaluating BODY. Otherwise, this evaluates BODY and
stores the evaluated value into STORE with KEY and EXPIRE. KEY is a
form that an evaluated value indicates the cache key.

Example:

    (defun f (x)
      (with-cache (x *store*)
        (very-complex-computation x)))"
  (once-only (key store expire)
    (with-gensyms (value exists-p)
      `(multiple-value-bind (,value ,exists-p)
           (getcache ,key ,store)
         (if ,exists-p
             ,value
             (let ((,value (progn ,@body)))
               (setcache ,key ,value ,store :expire ,expire)
               ,value))))))

@export
(defmacro with-inline-cache ((key &key expire (test 'equal) weakness) &body body)
  "Same as WITH-CACHE, except that an inline memory store will be used
as a cache store. TEST is a function to test hash table keys of the
memory store. WEAKNESS specifies the hash table is weak-hash-table or
not."
  (let* ((hash-table-form `(trivial-garbage:make-weak-hash-table :test (quote ,test) :weakness ,weakness))
         (store-form `(make-instance 'memory-store :hash-table ,hash-table-form)))
    `(with-cache (,key :store (load-time-value ,store-form) :expire ,expire)
       ,@body)))

@export
@annotation (:arity 2)
(defmacro cache ((keyargs &key store expire) function-definition-form)
  "Annotation for caching functions with their arguments. This should
be used with CL-ANNOT. KEYARGS is a form or a list of form for making
a cache key.  To make cache keys distinct as to the function, you may
add a keyword or a symbol into KEYARGS. See also WITH-CACHE.

Example:

    @cache ((:f x y z))
    (defun f (x y z)
      ...)
    
    ;; Remove a cache of F
    (remcache '(:f 1 2 3))"
  (replace-function-body
   (lambda (name lambda-list body)
     @ignore name
     @ignore lambda-list
     (let ((key (if (listp keyargs)
                    `(list ,@keyargs)
                    keyargs)))
       `(with-cache (,key :store ,store :expire ,expire)
          ,@body)))
   function-definition-form))