Re: Procedures (interfaces) Taylor Campbell 22 Oct 2003 20:36 UTC

On Tuesday, Oct 21, 2003, at 03:20 US/Eastern, Bradd W. Szonye wrote:

> As promised, here is my suggestion for revising the collection
> procedures. I tried to remain faithful to the original, and I tried to
> avoid even minor changes. No, really! Nevertheless, I'm sure that it
> will look like a big change. Therefore, let me preface my revision with
> some rationales. Feel free to question or reject them -- as you've
> already seen, I'm amenable to leaving things the way they are if you
> have a good reason to do so.
>
> Organization - With the new attributes like reversible, it was too
> difficult to organize procedures by collection type. Instead, I list
> them all by procedure type (constructor, accessor, etc.) with a
> "defined
> for:" clause below each usage example. I think I correctly specified
> all
> of the interfaces, but please double-check them if you decide to use
> this.
>
> Dictionaries - I generalized the collection-building procedures to work
> well with dictionaries. For example, *-add can now build a dictionary;
> just give it a (cons key value) pair, like you do in the dictionary
> constructor. Likewise, *-remove can remove a mapped value; it returns
> the removed key and value as a pair. There's also a specialized
> *-add-key (for convenience's sake) and *-remove-key (for necessity's
> sake). I removed the *-put function, but added a note to *-set-key that
> some dictionaries may permit key addition via that procedure. In other
> words, the "add-or-set" operation is optional.
>
> Removal and deletion - All remove-type procedures now return the
> modified collection and the removed value. If it's a dictionary, remove
> returns a key-value pair.
>
> Set! compatible with R5RS - The original specification for *-set! was
> incompatible with R5RS vector-set!, which modifies the vector in-place
> and returns an unspecified value. I changed *-set! to match that, and I
> changed *-set (the functional version) to return the modified
> collection
> and the previous value.

This doesn't work at all, because *-SET! is _linear_update_, not
purely mutable.  I am _adamantly_ against removing linear update
procedures and replacing them with purely mutable ones, because it
just _doesn't_work_ with some kinds of collections, e.g. lists, where
the empty list can't be mutated.  (This applies to all of the other
*-foo! procedures as well.)

> *=? and <=?: I added question marks for consistency with functions like
> string=?. I also removed the elt= argument and changed the definition
> so
> that it was no longer necessary. I was reluctant to make this change,
> but I felt that the original version just wasn't viable.

I don't like this at all.  Removing ELT= is removing a _lot_ of
functionality.  The reason the ? wasn't there was _because_ the
procedures were not comparable to STRING=?, CHAR=?, et cetera.

I don't understand the rationale for having ordering functions on
_collections_.  Can you explain this in more detail?  (or in any
detail at all: I can't find any)

>                                                          The main
> problem, once again, is that a single equivalence predicate just
> doesn't
> work for dictionaries.

It _does_ make sense for dictionaries: if all the keys are equal, _by_
the_dictionary's_key_equivalence_function_, then test to see if all
the

>                        I considered passing (cons key=? val=?) to the
> procedure, but that wouldn't work for non-dictionaries.

Er, I don't get this.  You didn't mind changing the add and remove
operations to accept pairs for dictionaries, but you do for the
comparison operations?

>                                                         In the end, I
> went with the *=? interface, partly because it makes for a trivial
> implementation of string=?.

(define (string=? . strings)
   (apply string= char=? strings))

That looks pretty trivial to me!

> I think that's all of the major changes. Here's the revision:
>
>
> PROCEDURES
>
> Many procedures accept an optional procedure (thunk) to handle failure.
> If a procedure cannot perform the requested operation (because the
> collection has a fixed size, is immutable, does not permit duplicate
> values, does not contain a requested value, would become unordered,
> etc.), it returns the value of invoking the thunk with no arguments.
> The
> default thunk signals an error.
>
> CONSTRUCTORS
> [...]
> procedure: % {key<? | key=?} {val<? | val=?} value ... => %
> defined for: all collections
>
> Constructs a new % collection with the values as its contents. Some
> collections may require ordering or equivalence functions during
> construction. Dictionaries may require comparison functions for both
> keys and values. (Ordered collections should derive their equivalence
> functions from the ordering function whenever possible.)

The ordering functions here sort of make sense, I guess...but I don't
like how they work with these two constructors.  It's ambiguous what
argument corresponds to what; for example, is (list = 1 2 3) a list
with = for a comparison function and (1 2 3) for contents, or a list
(#{Procedure} 1 2 3)?  (where '#{Procedure}' indicates an abstract
procedure value)

> procedure: *-copy * => *
> defined for: all collections
>
> Returns a new collection of the same type as the argument with the same
> contents but distinct storage. The copy is shallow; that is, it copies
> values in a way that preserves object identity.

I should like a *-COPY for sequences that also accepts optional start
and end arguments.

> TYPE PREDICATES
>
> procedure: collection? value => value
> procedure: %? value => value
> defined for: all collections
>
> Returns a true value iff the provided value supports the interface for
> the type (%) named in the predicate.

Why %? and not *??

> CARDINALITY OPERATORS
>
> procedure: *-empty? collection => boolean

Don't forget the 'defined for: all collections' bit.

> [...]
> procedure: *-size collection => exact integer
> defined for: all collections
>
> If the collection has a concept of size, this function returns the
> number of values or mappings in the collection. If it does not, #f must
> be returned. If possible, this should return the number of times
> collection-fold will call the fold-function before halting.

Have we decided on the issues of infiniteness and circularity yet?

> [...]
>
> EQUIVALENCE AND TOTAL ORDERING RELATIONS
>
> procedure: *=? *... => boolean
> procedure: *<? *... => boolean

(see far above)

> procedure: *-equivalence-function * => procedure
> defined for: bags, sets, and dictionaries

Where's the rationale for defining this _and_
*-KEY-EQUIVALENCE-FUNCTION on dictionaries?

> Returns the value equivalence function for the collection.
>
> procedure: *-key-equivalence-function * => procedure
> defined for: dictionaries
>
> Returns the key equivalence function for the dictionary.
>
> procedure: *-ordering-function * => procedure
> defined for: ordered bags, sets, and dictionaries

Again, I don't see why this is defined on dictionaries when there is
*-KEY-ORDERING-FUNCTION.

> Returns the value ordering function for the collection.
>
> procedure: *-key-ordering-function * => procedure
> defined for: ordered dictionaries
>
> Returns the key ordering function for the dictionary.
>
> ENUMERATIONS
> [...]
>
> ACCESSORS
> [...]
>
> VALUE MUTATORS
>

See near the top of this email about this.

> procedure: *-update * position f [thunk] => *
> procedure: *-update! * position f [thunk] => unspecified
> defined for: sequences and ordered collections
>
> procedure: *-update-key * key f [thunk] => *
> procedure: *-update-key! * key f [thunk] => unspecified
> defined for: sequences and ordered collections

I don't understand why you're differentiating between these two, and
why they aren't defined for dictionaries.

> COLLECTION MUTATORS
>
> When adding or inserting a value into a dictionary, the new value must
> be a key-value pair, unless the procedure is specifically defined for
> dictionaries. Likewise, when removing or deleting a dictionary value,
> the returned value is a key-value pair. A key-value pair is a Scheme
> pair whose car is the key and whose cdr is the mapped value:
> (cons key value).
>
> procedure: *-clear[!] * [thunk] => *
> defined for: all collections
>
> Returns an empty collection of the same type as the argument.

[the next bit is permutated and abbreviated bit]

> procedure: *-add-key[!] * new-key mapped-value [thunk] => *
> defined for: dictionaries
>
> Returns a collection of the same type with the new key mapped to the
> given value.

Why have *-ADD with key/value pairs _and_ this?

> procedure: *-add-each[!] * source-collection [thunk] => *
> procedure: *-remove-every[!] * value => * list
> procedure: *-remove-each-from[!] * source-collection [thunk] => % list
> procedure: *-remove-every-from[!] * source-collection [thunk] => % list
> procedure: *-remove-key[!] * key [thunk] => * pair

Why differentiate between removing and removing keys?

> procedure: *-remove-every-key[!] * key [thunk] => * list
> procedure: *-remove-each-key-from[!] * source [thunk] => * list
> procedure: *-remove-every-key-from[!] * source [thunk] => * list

Er, why all of the name changes?

> [...]
> procedure: *-insert-right * new-value [thunk] => *
> defined for: reversible sequences

Don't you mean 'reversible _flexible_ sequences?'

> [...]
> procedure: *-delete-right * [thunk] => *
> defined for: reversible sequences and reversible ordered collections

[flexible]

> As *-delete, except that it deletes the value at the end of the
> sequence.
>
> SET OPERATIONS
> [...]
> --
> Bradd W. Szonye
> http://www.szonye.com/bradd
>