srfi-69.scm 13 KB

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