summaryrefslogtreecommitdiffstats
path: root/spec.md
blob: 87563d2c4a242ff0b1898558b32e781f41e19af1 (plain) (blame)
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
## Abstract

The procedures of this SRFI allow callers to
manipulate an object that maps keys to values
without the caller needing to know exactly what the type
of the object is.  Such an object is called a *dictionary* in this SRFI.

## Rationale

Until recently there was only one universally available mechanism for managing
key-value pairs: alists.  Most Schemes also support hash tables, but
until R6RS there was no standard interface to them, and many
implementations do not provide that interface.

Now, however, the number of such mechanisms is
growing.  In addition to both R6RS and R7RS hash tables, there are
persistent ordered and hashed mappings from SRFI 146
and ordered key-value stores (often on a disk or a remote machine)
from SRFI 167.

It's inconvenient for users if SRFIs or other libraries have to
insist on accepting only a specific type of dictionary.
This SRFI exposes
a number of accessors, mutators, and other procedures that can be
called on any dictionary, provided that its type has been registered
with an implementation of this SRFI.

This in turn requires that the dictionary type provides a predicate
that can recognize it, plus at least these primitive operations:
determine a dictionary's current size; reference, update, or insert
an element of the dictionary depending on its current contents;
map over all the elements with
a function mapping the old value to a new one;
filter the elements based on their keys or values;
and process all the elements using a side-effecting procedure.

By using the procedures of this SRFI, a procedure can take a dictionary
as an argument and make flexible use of it without knowing its exact type.

Note that dictionaries must still be constructed using type-specific
constructors, as the required and optional arguments differ in each case.

## Specification

In order for the system to know that an object is a dictionary,
a predicate must be defined that recognizes that type of dictionary.
Then the predicate must be registered along with procedures that know
how to manipulate the dictionary.  At least the six basic dictionary procedures
(see below) must be registered, but more may be provided at registration time.

We call a specific key-value pair an *association*.  This is
why an alist, or association list, is called that; it is a list
of associations represented as pairs.

When a key argument is said to be the same as some key of the dictionary,
it means that they are the same in the sense of the dictionary's equality predicate.
It is assumed that no dictionary contains two keys that are the same in this sense.

Dictionaries are said in this SRFI to be *similar* if they are of the same
type and have the same [SRFI 128](http://srfi.schemers.org/srfi-128/srfi-128.html)
comparator.

## Predicates

`(dictionary? `*obj*`)`

Returns `#t` if *obj* answers `#t` to some registered predicate,
and `#f` otherwise.

`(dict-empty? `*dictionary*`)`

Returns `#t` if *dictionary* contains no associations
and `#f` if it does contain associations.

`(dict-contains? `*dictionary key*`)`

Returns `#t `if one of the keys of *dictionary* is the same as *key*
and `#f` otherwise.

## Lookup

`(dict-ref 	`*dictionary key* [*failure* [*success*] ]`)`

If *key* is the same as
some key of *dictionary*,
then invokes *success* on the corresponding value and returns its result.
If *key* is not a key of *dictionary*,
then invokes the thunk *failure* and returns its result.
The default value of *failure* signals an error;
the default value of *success* is the identity procedure.

`(dict-ref/default `*dictionary key default*`)`

If *key* is the same as some key of *dictionary*
then returns the corresponding value.
If not, then returns *default*.

## Mutation

All these procedures are linear-update: that is, they may return a new
dictionary object (which may or may not share storage with the *dictionary*
argument), or the same dictionary object, mutated.  In either case,
it is an error to access the dictionary later through any other reference to it,
as that reference may have been invalidated.

`(dict-set! `*dictionary obj* ...`)`

Returns a dictionary that contains all the associations of *dictionary*
plus those specified by *objs*, which alternate between keys and values.
If a key to be added already exists in *dictionary*, the new value prevails.

`(dict-adjoin! `dictionary obj ...`)`

Returns a dictionary that contains all the associations of *dictionary*
plus those sp8ecified by *objs*, which alternate between keys and values.
If a key to be added already exists in *dictionary*, the old value prevails.

`(dict-delete! `*dictionary key* ...`)`

Returns a dictionary that contains all the associations of *dictionary* except those
whose keys are the same as one of the *keys*.

`(dict-delete-all! `*dictionary keylist*`)`

Returns a dictionary with all the associations of *dictionary* except those whose
keys are the same as some member of *keylist*.

`(dict-replace! `*dictionary key value*`)`

Returns a dictionary that
contains all the associations of *dictionary* except as follows:
If *key* is the same as a key of *dictionary*,
then the association for that key is omitted and replaced by the association
defined by the pair *key* and *value*.
If there is no such key in *dictionary*,
then dictionary is returned unchanged.

`(dict-intern! `dictionary key failure`)`

Extracts the value associated with the key in *dictionary* that is the same as *key*,
and returns two values, *dictionary* and the value.
If *key* is not the same as any key in *dictionary*, *failure* is invoked on no arguments.

The procedure then returns two values,
a dictionary that contains all the associations of *dictionary*
and in addition a new association that maps *key* to the result of invoking *failure*, 
and the result of invoking *failure*.

`(dict-update! `*dictionary key updater* [*failure* [*success*] ]`)`

Retrieves the value of *key* as if by `dict-ref`,
invokes *updater* on it, and sets the value of *key* to be
the result of calling *updater* as if by `dict-set`,
but may do so more efficiently.  Returns the updated dictionary.
The default value of *failure* signals an error;
the default value of *success* is the identity procedure.

`(dict-update/default! `*dictionary key updater default*`)`

Retrieves the value of *key* as if by `dict-ref/default`,
invokes *updater* on it, and sets the value of *key* to be
the result of calling *updater* as if by `dict-set`,
but may do so more efficiently.  Returns the updated dictionary.

`(dict-pop! `*dictionary* [*failure*]`)`

Chooses an  association from *dictionary* and returns three values:
a dictionary that contains all associations of *dictionary* except the chosen one,
and the key and the value of the chosen association.
If the dictionary is ordered, the first association is chosen;
otherwise the chosen association is arbitrary.

If dictionary contains no associations and *failure* is supplied,
then the thunk *failure* is invoked and its values returned.
Otherwise, it is an error.

`(dict-map! `*proc dictionary*`)`

Returns a dictionary similar to *dictionary* that maps each key of *dictionary*
to the value that results
from invoking *proc* on the corresponding key and value of *dictionary*.

`(dict-filter! `*pred dictionary*`)`  

Returns a dictionary similar to *dictionary* that contains just the associations of *dictionary*
that satisfy *pred* when it is invoked on the key and value of the association.

`(dict-remove! `*pred dictionary*`)`

Returns a dictionary that contains all the associations of *dictionary*
except those that satisfy *pred* when called on the key and value.

`(dict-search! `*dictionary key failure success*`)`

This procedure is a workhorse for dictionary lookup, insert, and delete.
The dictionary *dictionary* is searched
for an association whose key is the same as *key*
in the sense of *dictionary*'s comparator.
If one is not found, then the *failure* procedure is tail-called
with two continuation arguments, *insert* and *ignore*,
and is expected to tail-call one of them.

However, if such an association is found,
then the *success* procedure is tail-called
with the matching key of *dictionary*, the associated value,
and two continuation arguments, *update* and *remove*,
and is expected to tail-call one of them.

It is an error if the continuation arguments are invoked other than
in tail position in the *failure* and *success* procedures.
It is also an error if the *failure* and *success* procedures
return to their implicit continuation without invoking
one of their arguments.

The behaviors of the continuations are as follows
(where *obj* is any Scheme object):

 *  Invoking `(`*insert value obj*`)` returns a dictionary that
    contains all the associations of *dictionary*,
    and in addition a new association that maps *key* to *value*.

 *  Invoking `(`*ignore obj*`)` has no effects and returns *dictionary*
    unchanged.

 *  Invoking `(`*update new-key new-value obj*`)` returns a dictionary that
    contains all the associations of *dictionary*,
    except for the association whose key is the same as *key*,
    which is replaced or hidden by a new association
    that maps *new-key* to *new-value*.
    It is an error if *key* and *new-key* are not the same
    in the sense of the dictionary's equality predicate.

 *  Invoking `(`*remove obj*`)` returns a dictionary that
    contains all the associations of *dictionary*,
    except for the association with key key.

In all cases, *obj* is returned as a second value.

## The whole dictionary

`(dict-size `*dictionary*`)`

Returns an exact integer representing the number of associations in *dictionary*.

`(dict-for-each `*proc dictionary*`)`

Invokes *proc* on each key of *dictionary* and its corresponding value in that order.
This procedure is used for doing operations on the whole dictionary.
If the dictionary type is inherently ordered, associations are processed in the
inherent order; otherwise in an arbitrary order.
Returns an unspecified value.

`(dict-count `*pred dictionary*`)`

Passes each association of dictionary as two arguments to *pred*
and returns the number of times
that *pred* returned true as an an exact integer.

`(dict-any `*pred dictionary*`)`

Passes each association of *dictionary* as two arguments to *pred*
and returns the value of the first call to *pred* that returns true.
If the dictionary type is inherently ordered, associations are processed in the
inherent order; otherwise in an arbitrary order.
If all calls return false, `dict-any` returns false.

`(dict-every `*pred dictionary*`)`

Passes each association of *dictionary* as two arguments to *pred*
and returns `#f` after the first call to *pred* that returns false.
If the dictionary type is inherently ordered, associations are processed in the
inherent order; otherwise in an arbitrary order.
If all calls return true, `dict-any` returns the value of the last call,
or `#t` if no calls are made.

`(dict-keys `*dictionary*`)`

Returns a list of the keys of *dictionary*.
If the dictionary type is inherently ordered, associations are processed in the
inherent order; otherwise in an arbitrary order.
The order may change when new elements are added to *dictionary*.

`(dict-values `*dictionary*`)`

Returns a list of the values of *dictionary*.  The results returned
by `dict-keys` and `dict-values` are ordered consistently.

`(dict-entries `*dictionary*`)`

Returns two values, the result of calling `dict-keys` and the
result of calling `dict-values` on *dictionary*.

`(dict-fold `*proc knil dictionary*`)`

Invokes *proc* on each association of *dictionary* with three arguments:
the key of the association, the value of the association,
and an accumulated result of the previous invocation. 
For the first invocation, *knil* is used as the third argument.
Returns the result of the last invocation,
or *knil* if there was no invocation.

`(dict-map->list `*proc dictionary*`)`

Returns a list of values that result from invoking *proc*
on the keys and corresponding values of *dictionary*.

`(dict->alist `*dictionary*`)`

Returns an alist whose keys and values are the keys and values of *dictionary*.

## Registering dictionary types

The following procedure registers new dictionary types.
It is an error to register a dictionary type whose
instances return `#t` to any predicate already registered.

`(register-dictionary! `*arg* ...`)`

Registers a new dictionary type, providing procedures that allow
manipulation of dictionaries of that type.
The *args* are alternately *procnames* and corresponding *procs*.

A *procname* argument is a symbol which is the same as one
of the procedures defined in this SRFI (other than
`register-dictionary!` itself), and a *proc* argument
is the specific procedure implementing it for this type.
These procedures only need to handle the full argument list
when defining `dict-ref`, `dict-update!`, and `dict-pop!`, as the
defaults have already been supplied by the framework.

Arguments for the six procedures `dictionary?`, `dict-size`,
`dict-search!`, `dict-map!`, `dict-filter!`, and `dict-for-each` are required.
The others are optional, but if provided can be more efficient
than the versions automatically provided by the implementation of this SRFI.

## Lists as dictionaries

The exact set of pre-registered dictionaries depends on their
availability in a given implementation.  However, lists are
supported as dictionaries using the specification in this section.
If two keys are the same (in the sense of the specified equality predicate),
then all but the first are treated as if they did not exist.

If the car of a list is a symbol, then the list is assumed to be a property
list, alternating symbol keys with values.
Mutation operations actually mutate the property list whenever possible.
The equality predicate of this type of dictionary is `eq?`.

If a list is empty, or its car is a pair, then the list is assumed
to be an alist.  New values are added to the beginning of an alist
and the new alist is returned;
deletion does not mutate the alist, but returns an alist
that may or may not share storage with the original alist.
If an association has been updated, then both the new and the old
association may be processed by the whole-dictionary procedures.
The equality predicate of this type of dictionary is `equal?`.

In all other cases, lists are not treated as dictionaries
unless an appropriate dictionary type has been registered.

## Implementation

The sample implementation of this SRFI can be found in its repository.
The following list of dependencies is designed to ease registering
new dictionary types that may not have complete dictionary APIs:


 * `dict-empty?` depends on `dict-size`
 * `dict-contains?` depends on `dict-ref`
 * `dict-ref` depends on `dict-search!`
 * `dict-ref/default` depends on `dict-ref`
 * `dict-set!` depends on `dict-search!`
 * `dict-adjoin!` depends on `dict-search!`
 * `dict-delete!` depends on `dict-delete-all!`
 * `dict-delete-all!` depends on `dict-search!`
 * `dict-replace!` depends on `dict-search!`
 * `dict-intern!` depends on `dict-search!`
 * `dict-update!` depends on `dict-search!`
 * `dict-update/default!` depends on `dict-update!`
 * `dict-pop!` depends on `dict-for-each`, `dict-delete!` and `dict-empty?`
 * `dict-remove!` depends on `dict-filter!`
 * `dict-count` depends on `dict-fold`
 * `dict-any` depends on `dict-for-each`
 * `dict-every` depends on `dict-for-each`
 * `dict-keys` depends on `dict-fold`
 * `dict-values` depends on `dict-fold`
 * `dict-entries` depends on `dict-fold`
 * `dict-fold` depends on `dict-for-each`
 * `dict-map->list` depends on `dict-fold`
 * `dict->alist` depends on `dict-map->list`

For example, the first dependency means that if an implementation
being registered has something corresponding to `dict-ref` it need not
also supply `dict-contains?`, because its default implementation
uses `dict-ref`.  This might not be true in other implementations.