defsubst
[bpt/guile.git] / module / srfi / srfi-69.scm
1 ;;; srfi-69.scm --- Basic hash tables
2
3 ;; Copyright (C) 2007 Free Software Foundation, Inc.
4 ;;
5 ;; This library is free software; you can redistribute it and/or
6 ;; modify it under the terms of the GNU Lesser General Public
7 ;; License as published by the Free Software Foundation; either
8 ;; version 3 of the License, or (at your option) any later version.
9 ;;
10 ;; This library is distributed in the hope that it will be useful,
11 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 ;; Lesser General Public License for more details.
14 ;;
15 ;; You should have received a copy of the GNU Lesser General Public
16 ;; License along with this library; if not, write to the Free Software
17 ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 \f
19 ;;;; Commentary:
20
21 ;; My `hash' is compatible with core `hash', so I replace it.
22 ;; However, my `hash-table?' and `make-hash-table' are different, so
23 ;; importing this module will warn about them. If you don't rename my
24 ;; imports, you shouldn't use both my hash tables and Guile's hash
25 ;; tables in the same module.
26 ;;
27 ;; SRFI-13 `string-hash' and `string-hash-ci' have more arguments, but
28 ;; are compatible with my `string-hash' and `string-ci-hash', and are
29 ;; furthermore primitive in Guile, so I use them as my own.
30 ;;
31 ;; I also have the extension of allowing hash functions that require a
32 ;; second argument to be used as the `hash-table-hash-function', and use
33 ;; these in defaults to avoid an indirection in the hashx functions. The
34 ;; only deviation this causes is:
35 ;;
36 ;; ((hash-table-hash-function (make-hash-table)) obj)
37 ;; error> Wrong number of arguments to #<primitive-procedure hash>
38 ;;
39 ;; I don't think that SRFI 69 actually specifies that I *can't* do this,
40 ;; because it only implies the signature of a hash function by way of the
41 ;; named, exported hash functions. However, if this matters enough I can
42 ;; add a private derivation of hash-function to the srfi-69:hash-table
43 ;; record type, like associator is to equivalence-function.
44 ;;
45 ;; Also, outside of the issue of how weak keys and values are referenced
46 ;; outside the table, I always interpret key equivalence to be that of
47 ;; the `hash-table-equivalence-function'. For example, given the
48 ;; requirement that `alist->hash-table' give earlier associations
49 ;; priority, what should these answer?
50 ;;
51 ;; (hash-table-keys
52 ;; (alist->hash-table '(("xY" . 1) ("Xy" . 2)) string-ci=?))
53 ;;
54 ;; (let ((ht (make-hash-table string-ci=?)))
55 ;; (hash-table-set! ht "xY" 2)
56 ;; (hash-table-set! ht "Xy" 1)
57 ;; (hash-table-keys ht))
58 ;;
59 ;; My interpretation is that they can answer either ("Xy") or ("xY"),
60 ;; where `hash-table-values' will of course always answer (1), because
61 ;; the keys are the same according to the equivalence function. In this
62 ;; implementation, both answer ("xY"). However, I don't guarantee that
63 ;; this won't change in the future.
64
65 ;;; Code:
66 \f
67 ;;;; Module definition & exports
68
69 (define-module (srfi srfi-69)
70 #:use-module (srfi srfi-1) ;alist-cons,second&c,assoc
71 #:use-module (srfi srfi-9)
72 #:use-module (srfi srfi-13) ;string-hash,string-hash-ci
73 #:use-module (ice-9 optargs)
74 #:export (;; Type constructors & predicate
75 make-hash-table hash-table? alist->hash-table
76 ;; Reflective queries
77 hash-table-equivalence-function hash-table-hash-function
78 ;; Dealing with single elements
79 hash-table-ref hash-table-ref/default hash-table-set!
80 hash-table-delete! hash-table-exists? hash-table-update!
81 hash-table-update!/default
82 ;; Dealing with the whole contents
83 hash-table-size hash-table-keys hash-table-values
84 hash-table-walk hash-table-fold hash-table->alist
85 hash-table-copy hash-table-merge!
86 ;; Hashing
87 string-ci-hash hash-by-identity)
88 #:re-export (string-hash)
89 #:replace (hash make-hash-table hash-table?))
90
91 (cond-expand-provide (current-module) '(srfi-69))
92 \f
93 ;;;; Internal helper macros
94
95 ;; Define these first, so the compiler will pick them up.
96
97 ;; I am a macro only for efficiency, to avoid varargs/apply.
98 (define-macro (hashx-invoke hashx-proc ht-var . args)
99 "Invoke HASHX-PROC, a `hashx-*' procedure taking a hash-function,
100 assoc-function, and the hash-table as first args."
101 `(,hashx-proc (hash-table-hash-function ,ht-var)
102 (ht-associator ,ht-var)
103 (ht-real-table ,ht-var)
104 . ,args))
105
106 (define-macro (with-hashx-values bindings ht-var . body-forms)
107 "Bind BINDINGS to the hash-function, associator, and real-table of
108 HT-VAR, while evaluating BODY-FORMS."
109 `(let ((,(first bindings) (hash-table-hash-function ,ht-var))
110 (,(second bindings) (ht-associator ,ht-var))
111 (,(third bindings) (ht-real-table ,ht-var)))
112 . ,body-forms))
113
114 \f
115 ;;;; Hashing
116
117 ;;; The largest fixnum is in `most-positive-fixnum' in module (guile),
118 ;;; though not documented anywhere but libguile/numbers.c.
119
120 (define (caller-with-default-size hash-fn)
121 "Answer a function that makes `most-positive-fixnum' the default
122 second argument to HASH-FN, a 2-arg procedure."
123 (lambda* (obj #:optional (size most-positive-fixnum))
124 (hash-fn obj size)))
125
126 (define hash (caller-with-default-size (@ (guile) hash)))
127
128 (define string-ci-hash string-hash-ci)
129
130 (define hash-by-identity (caller-with-default-size hashq))
131 \f
132 ;;;; Reflective queries, construction, predicate
133
134 (define-record-type srfi-69:hash-table
135 (make-srfi-69-hash-table real-table associator size weakness
136 equivalence-function hash-function)
137 hash-table?
138 (real-table ht-real-table)
139 (associator ht-associator)
140 ;; required for O(1) by SRFI-69. It really makes a mess of things,
141 ;; and I'd like to compute it in O(n) and memoize it because it
142 ;; doesn't seem terribly useful, but SRFI-69 is final.
143 (size ht-size ht-size!)
144 ;; required for `hash-table-copy'
145 (weakness ht-weakness)
146 ;; used only to implement hash-table-equivalence-function; I don't
147 ;; use it internally other than for `ht-associator'.
148 (equivalence-function hash-table-equivalence-function)
149 (hash-function hash-table-hash-function))
150
151 (define (guess-hash-function equal-proc)
152 "Guess a hash function for EQUAL-PROC, falling back on `hash', as
153 specified in SRFI-69 for `make-hash-table'."
154 (cond ((eq? equal? equal-proc) (@ (guile) hash)) ;shortcut most common case
155 ((eq? eq? equal-proc) hashq)
156 ((eq? eqv? equal-proc) hashv)
157 ((eq? string=? equal-proc) string-hash)
158 ((eq? string-ci=? equal-proc) string-ci-hash)
159 (else (@ (guile) hash))))
160
161 (define (without-keyword-args rest-list)
162 "Answer REST-LIST with all keywords removed along with items that
163 follow them."
164 (let lp ((acc '()) (rest-list rest-list))
165 (cond ((null? rest-list) (reverse! acc))
166 ((keyword? (first rest-list))
167 (lp acc (cddr rest-list)))
168 (else (lp (cons (first rest-list) acc) (cdr rest-list))))))
169
170 (define (guile-ht-ctor weakness)
171 "Answer the Guile HT constructor for the given WEAKNESS."
172 (case weakness
173 ((#f) (@ (guile) make-hash-table))
174 ((key) make-weak-key-hash-table)
175 ((value) make-weak-value-hash-table)
176 ((key-or-value) make-doubly-weak-hash-table)
177 (else (error "Invalid weak hash table type" weakness))))
178
179 (define (equivalence-proc->associator equal-proc)
180 "Answer an `assoc'-like procedure that compares the argument key to
181 alist keys with EQUAL-PROC."
182 (cond ((or (eq? equal? equal-proc)
183 (eq? string=? equal-proc)) (@ (guile) assoc))
184 ((eq? eq? equal-proc) assq)
185 ((eq? eqv? equal-proc) assv)
186 (else (lambda (item alist)
187 (assoc item alist equal-proc)))))
188
189 (define* (make-hash-table
190 #:optional (equal-proc equal?)
191 (hash-proc (guess-hash-function equal-proc))
192 #:key (weak #f) #:rest guile-opts)
193 "Answer a new hash table using EQUAL-PROC as the comparison
194 function, and HASH-PROC as the hash function. See the reference
195 manual for specifics, of which there are many."
196 (make-srfi-69-hash-table
197 (apply (guile-ht-ctor weak) (without-keyword-args guile-opts))
198 (equivalence-proc->associator equal-proc)
199 0 weak equal-proc hash-proc))
200
201 (define (alist->hash-table alist . mht-args)
202 "Convert ALIST to a hash table created with MHT-ARGS."
203 (let* ((result (apply make-hash-table mht-args))
204 (size (ht-size result)))
205 (with-hashx-values (hash-proc associator real-table) result
206 (for-each (lambda (pair)
207 (let ((handle (hashx-get-handle hash-proc associator
208 real-table (car pair))))
209 (cond ((not handle)
210 (set! size (1+ size))
211 (hashx-set! hash-proc associator real-table
212 (car pair) (cdr pair))))))
213 alist))
214 (ht-size! result size)
215 result))
216 \f
217 ;;;; Accessing table items
218
219 ;; We use this to denote missing or unspecified values to avoid
220 ;; possible collision with *unspecified*.
221 (define ht-unspecified (cons *unspecified* "ht-value"))
222
223 (define (hash-table-ref ht key . default-thunk-lst)
224 "Lookup KEY in HT and answer the value, invoke DEFAULT-THUNK if KEY
225 isn't present, or signal an error if DEFAULT-THUNK isn't provided."
226 (let ((result (hashx-invoke hashx-ref ht key ht-unspecified)))
227 (if (eq? ht-unspecified result)
228 (if (pair? default-thunk-lst)
229 ((first default-thunk-lst))
230 (error "Key not in table" key ht))
231 result)))
232
233 (define (hash-table-ref/default ht key default)
234 "Lookup KEY in HT and answer the value. Answer DEFAULT if KEY isn't
235 present."
236 (hashx-invoke hashx-ref ht key default))
237
238 (define (hash-table-set! ht key new-value)
239 "Set KEY to NEW-VALUE in HT."
240 (let ((handle (hashx-invoke hashx-create-handle! ht key ht-unspecified)))
241 (if (eq? ht-unspecified (cdr handle))
242 (ht-size! ht (1+ (ht-size ht))))
243 (set-cdr! handle new-value))
244 *unspecified*)
245
246 (define (hash-table-delete! ht key)
247 "Remove KEY's association in HT."
248 (with-hashx-values (h a real-ht) ht
249 (if (hashx-get-handle h a real-ht key)
250 (begin
251 (ht-size! ht (1- (ht-size ht)))
252 (hashx-remove! h a real-ht key))))
253 *unspecified*)
254
255 (define (hash-table-exists? ht key)
256 "Return whether KEY is a key in HT."
257 (and (hashx-invoke hashx-get-handle ht key) #t))
258
259 ;;; `hashx-set!' duplicates the hash lookup, but we use it anyway to
260 ;;; avoid creating a handle in case DEFAULT-THUNK exits
261 ;;; `hash-table-update!' non-locally.
262 (define (hash-table-update! ht key modifier . default-thunk-lst)
263 "Modify HT's value at KEY by passing its value to MODIFIER and
264 setting it to the result thereof. Invoke DEFAULT-THUNK for the old
265 value if KEY isn't in HT, or signal an error if DEFAULT-THUNK is not
266 provided."
267 (with-hashx-values (hash-proc associator real-table) ht
268 (let ((handle (hashx-get-handle hash-proc associator real-table key)))
269 (cond (handle
270 (set-cdr! handle (modifier (cdr handle))))
271 (else
272 (hashx-set! hash-proc associator real-table key
273 (if (pair? default-thunk-lst)
274 (modifier ((car default-thunk-lst)))
275 (error "Key not in table" key ht)))
276 (ht-size! ht (1+ (ht-size ht)))))))
277 *unspecified*)
278
279 (define (hash-table-update!/default ht key modifier default)
280 "Modify HT's value at KEY by passing its old value, or DEFAULT if it
281 doesn't have one, to MODIFIER, and setting it to the result thereof."
282 (hash-table-update! ht key modifier (lambda () default)))
283 \f
284 ;;;; Accessing whole tables
285
286 (define (hash-table-size ht)
287 "Return the number of associations in HT. This is guaranteed O(1)
288 for tables where #:weak was #f or not specified at creation time."
289 (if (ht-weakness ht)
290 (hash-table-fold ht (lambda (k v ans) (1+ ans)) 0)
291 (ht-size ht)))
292
293 (define (hash-table-keys ht)
294 "Return a list of the keys in HT."
295 (hash-table-fold ht (lambda (k v lst) (cons k lst)) '()))
296
297 (define (hash-table-values ht)
298 "Return a list of the values in HT."
299 (hash-table-fold ht (lambda (k v lst) (cons v lst)) '()))
300
301 (define (hash-table-walk ht proc)
302 "Call PROC with each key and value as two arguments."
303 (hash-table-fold ht (lambda (k v unspec)
304 (call-with-values (lambda () (proc k v))
305 (lambda vals unspec)))
306 *unspecified*))
307
308 (define (hash-table-fold ht f knil)
309 "Invoke (F KEY VAL PREV) for each KEY and VAL in HT, where PREV is
310 the result of the previous invocation, using KNIL as the first PREV.
311 Answer the final F result."
312 (hash-fold f knil (ht-real-table ht)))
313
314 (define (hash-table->alist ht)
315 "Return an alist for HT."
316 (hash-table-fold ht alist-cons '()))
317
318 (define (hash-table-copy ht)
319 "Answer a copy of HT."
320 (with-hashx-values (h a real-ht) ht
321 (let* ((size (hash-table-size ht)) (weak (ht-weakness ht))
322 (new-real-ht ((guile-ht-ctor weak) size)))
323 (hash-fold (lambda (k v ign) (hashx-set! h a new-real-ht k v))
324 #f real-ht)
325 (make-srfi-69-hash-table ;real,assoc,size,weak,equiv,h
326 new-real-ht a size weak
327 (hash-table-equivalence-function ht) h))))
328
329 (define (hash-table-merge! ht other-ht)
330 "Add all key/value pairs from OTHER-HT to HT, overriding HT's
331 mappings where present. Return HT."
332 (hash-table-fold
333 ht (lambda (k v ign) (hash-table-set! ht k v)) #f)
334 ht)
335
336 ;;; srfi-69.scm ends here