Module: internal Author: Jonathan Bachrach Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND // BOOTED: define ... class ... end; // Useful Functional Objects extensions // These constants are explicitly declared so that callers can // trust that they're pointer objects. Their actually "pairness" should // never be seen. // since pairs aren't inlineable, we use a thunk which is. define generic not-found-object (); define inline constant not-found = method () not-found-object end; define inline constant not-found? = method (x) x == not-found-object; end; // define inline constant found? = method (x) ~(x == not-found-object); end; define generic unsupplied-object (); define inline constant unsupplied = method () unsupplied-object end; define inline constant unsupplied? = method (x) x == unsupplied-object; end; define inline constant supplied? = method (x) ~(x == unsupplied-object); end; // Methods with only one collection argument are specialized in the // appropriate file. If a method has more than one collection argument // then all the specializations are presented together. //////////// // INTERFACE //////////// // Functions on . // Make these sealed generics so we can specialize if necessary. // We need to ensure that the dispatching of any uses of these functions // in the dispatch code is resolved at compile time. define sealed generic do (fn :: , coll :: , #rest more-colls :: ) => false :: singleton(#f); define sealed generic map (fn :: , coll :: , #rest more-colls :: ) => new-collection :: ; define constant = type-union(subclass(), ); define constant = type-union(subclass(), ); define sealed generic map-as (type :: , fn :: , collection :: , #rest more-collections :: ) => new-collection :: ; define sealed generic map-into (mutable-collection :: , function :: , collection :: , #rest more-collections :: ) => mutable-collection :: ; define sealed generic any? (test :: , coll :: , #rest more-colls :: ) => value; define sealed generic every? (test :: , coll :: , #rest more-colls :: ) => value :: ; // Open generics on define open generic element (collection :: , key, #key default) => object; define open generic key-sequence (collection :: ) => (keys :: ); define open generic reduce (fn :: , initial-value, collection :: ) => object; define open generic reduce1 (fn :: , collection :: ) => object; define open generic member? (value, collection :: , #key test) => boolean :: ; define open generic find-key (collection :: , predicate :: , #key skip, failure) => key; define open generic key-test (collection :: ) => (test :: ); define open generic forward-iteration-protocol (collection :: ) => (initial-state :: , limit :: , next-state :: , finished-state? :: , current-key :: , current-element :: , current-element-setter :: , copy-state :: ); define open generic backward-iteration-protocol (collection :: ) => (initial-state :: , limit :: , next-state :: , finished-state? :: , current-key :: , current-element :: , current-element-setter :: , copy-state :: ); define open generic add! (coll :: , new-element) => (possibly-new-coll :: ); define open generic remove! (coll :: , value, #key test, count) => (possibly-new-coll :: ); // Access conditions // TODO: need a better hierarchy than this. define class () end; define class () end; // define class () end; // define class () end; define class () end; define class () end; define class () end; define class () end; ///////////////// // IMPLEMENTATION ///////////////// // // FORWARD-ITERATION-PROTOCOL // /// support for set-by define inline function %curry-current-element-setter (collection, state, current-element-setter) method (new-value) current-element(collection, state) := new-value end method end function; // // DO // define sealed method do (fn :: , coll :: , #rest more-colls :: ) => false :: singleton(#f); if (more-colls.empty?) for (e in coll) fn(e) end else for (v in apply(multiple-collection, coll, more-colls)) apply(fn, v) end end if end method do; // The compiler optimizes a call to "do" with a single collection to // a call to do-one define generic do-one (function :: , collection :: ) => false :: singleton(#f); define inline method do-one (function :: , collection :: ) => (false :: singleton(#f)) for (e in collection) function(e) end end method do-one; do-one; // Silence "unused" warning. // // MAP // define generic map-as-one (type :: , function :: , collection :: ) => new-collection :: ; // actually :: type define sealed method map (fn :: , coll :: , #rest more-colls :: ) => new-collection :: ; let tfc :: = type-for-copy(coll); if (empty?(more-colls)) map-as-one(tfc, fn, coll) else map-as-one(tfc, method (v) apply(fn, v) end, apply(multiple-collection, coll, more-colls)) end if end method map; // The compiler optimizes a call to map with a single collection to // a call to map-as-one // // MAP-AS // define method map-as (type :: , function :: , collection :: , #rest more-collections :: ) => (result :: ); // actually :: type; if (empty?(more-collections)) map-as-one(type, function, collection) else // Might be worth splitting out the singleton case eventually? map-as-one(type, method (v) apply(function, v) end, apply(multiple-collection, collection, more-collections)) end if end method map-as; // First the two covering cases: // We don't know how big the new collection will be in advance, and it may // be expensive to precompute the size, so accumulate the result as we go // along. define method map-as-one (type :: , function :: , collection :: ) => new-collection :: ; // actually :: type let acc = make(); for (e keyed-by k in collection) acc[k] := function(e) end for; convert-accumulator-as(type, acc) end method map-as-one; define method map-as-one (type :: , function :: , collection :: ) => new-collection :: ; // actually :: type let acc = make(); for (e in collection) add!(acc, function(e)) end for; convert-accumulator-as(type, acc) end method map-as-one; // In the list and deque case we don't need to know the size in advance, // so no need for accumulators. define inline method map-as-one (type == , function :: , collection :: ) => new-collection :: ; for (result = #() then pair(function(e), result), e in collection) finally reverse!(result) end for; end method map-as-one; // If we have an or then we assume we can find the size // efficiently. define method map-as-one (type :: subclass(), function :: , collection :: ) => new-collection :: ; // actually :: type let collection-size = collection.size; if (collection-size = 0) make(type, size: 0) else let result = make(type, dimensions: collection.dimensions, fill: function(collection.first)); without-bounds-checks for (i :: from 1 below collection-size) result[i] := function(collection[i]) end for end without-bounds-checks; result end if end method map-as-one; define method map-as-one (type :: subclass(), function :: , collection :: ) => new-collection :: ; // actually :: type let collection-size = collection.size; if (collection-size = 0) make(type, size: 0) else let result = make(type, size: collection.size, fill: function(collection.first)); without-bounds-checks for (i :: from 1 below collection-size) result[i] := function(collection[i]) end for end without-bounds-checks; result end if end method map-as-one; /* define inline copy-down-method map-as-one (type == , function :: , collection :: ) => (new-collection :: ); */ define inline method map-as-one (type == , function :: , collection :: ) => new-collection :: ; // actually :: type let result = make(, size: collection.size); without-bounds-checks for (i :: from 0 below collection.size) result[i] := function(collection[i]) end for end without-bounds-checks; result end method map-as-one; // And now some tie-breakers... define copy-down-method map-as-one (type :: subclass(), function :: , collection :: ) => (new-collection :: ); define copy-down-method map-as-one (type == , function :: , collection :: ) => (new-collection :: ); /* define method map-as-one (type :: subclass(), function :: , collection :: ) => new-collection :: ; // actually :: type let acc = make(); for (e keyed-by k in collection) acc[k] := function(e) end for; convert-accumulator-as(type, acc) end method map-as-one; define method map-as-one (type == , function :: , collection :: ) => new-collection :: ; // actually :: type let acc = make(); for (e keyed-by k in collection) acc[k] := function(e) end for; convert-accumulator-as(type, acc) end method map-as-one; */ // // MAP-INTO // define method map-into (target :: , function :: , collection :: , #rest more-collections :: ) => (target :: ); if (empty?(more-collections)) unless (target.key-test == collection.key-test) error(make(, format-string: "Collections %= and %= have different key tests", format-arguments: list(target, collection))) end; if (instance?(target, )) map-into-stretchy-one(function, target, collection) else map-into-rigid-one(function, target, collection) end if else // Don't bother doing anything too smart for now. map-into(target, method (v) apply(function, v) end, apply(multiple-collection, collection, more-collections)) end if end method map-into; // STRETCHY CASE // QUESTION: // The target collection should not be involved in the alignment in the // stretchy case, but what if the target is a sequence and the source // contains keys that aren't natural numbers? For now we just ignore // such keys, so that the target has a "stretchy" effect on alignment. define method map-into-stretchy-one (fun :: , target :: , coll :: ) => (target :: ); for (val keyed-by key in coll) target[key] := fun(val) end for; target end; define copy-down-method map-into-stretchy-one (fun :: , target :: , coll :: ) => (target :: ); /* define method map-into-stretchy-one (fun :: , target :: , coll :: ) => (target :: ); for (val in coll, key from 0) target[key] := fun(val) end; target end; */ define method map-into-stretchy-one (fun :: , target :: , coll :: ) => (target :: ); let max-key = maximum-sequence-key(coll); with-fip-of target for (state = initial-state then next-state(target, state), key from 0 to max-key, until: finished-state?(target, state, limit)) let val = element(coll, key, default: not-found()); unless (not-found?(val)) current-element-setter(fun(val), target, state) end; finally if (key > max-key) target else // We are in trouble as the target isn't big enough. target.size := max-key + 1; // We can't continue the iteration as we have resized, so // start again, skipping the keys we have already processed. with-fip-of target for (key from 0 below key, state = initial-state then next-state(target, state)) finally // Process the remaining keys for (state = state then next-state(target, state), key from key to max-key) let val = element(coll, key, default: not-found()); unless (not-found?(val)) current-element-setter(fun(val), target, state) end end for end for end with-fip-of end if end for end with-fip-of; target end method map-into-stretchy-one; define method map-into-stretchy-one (fun :: , target :: , coll :: ) => (target :: ); with-fip-of coll with prefix c- with-fip-of target with prefix t- let t-state = t-initial-state; for (c-state = c-initial-state then c-next-state(coll, c-state), key from 0, until: c-finished-state?(coll, c-state, c-limit)) if (t-finished-state?(target, t-state, t-limit)) // Arghh. Now things are really grim. target.size := coll.size; // We can't continue the iteration on target as we have resized, so // start again, skipping the keys we have already processed. with-fip-of target with prefix t- for (key from 0 below key, t-state = t-initial-state then t-next-state(target, t-state)) finally // Process the remaining keys for (t-state = t-state then t-next-state(target, t-state)) t-current-element-setter (fun(c-current-element(coll, c-state)), target, t-state); c-state := c-next-state(coll, c-state); end for end for end else t-current-element-setter (fun(c-current-element(coll, c-state)), target, t-state); t-state := t-next-state(target, t-state) end if end for end end; target end method map-into-stretchy-one; // Subclasses of array should have sublinear implementations of element, // so let's exploit this. // Perhaps it would be better to find the maximum key to avoid repeated // expansions in the worst case? Or will implementations of things like // be smart about this? define method map-into-stretchy-one (fun :: , target :: , coll :: ) => (target :: ); for (val keyed-by key in coll) unless (~instance?(key, ) | key < 0) target[key] := fun(val) end; end for; target end; // If the source is a sequence then life is even easier... define method map-into-stretchy-one (fun :: , target :: , coll :: ) => (target :: ); for (val in coll, key from 0) target[key] := fun(val) end; target end; // some more useful ones define inline copy-down-method map-into-stretchy-one (fun :: , target :: , coll :: ) => (target :: ); define inline copy-down-method map-into-stretchy-one (fun :: , target :: , coll :: ) => (target :: ); // RIGID CASE // First we define the default case. define method map-into-rigid-one (fun :: , target :: , coll :: ) => (target :: ); for (val keyed-by key in coll) unless (not-found?(element(target, key, default: not-found()))) target[key] := fun(val) end end for; target end; // When the source or target is a sequence then iterate over that. define method map-into-rigid-one (fun :: , target :: , coll :: ) => (target :: ); let max-key = maximum-sequence-key(coll); with-fip-of target /* Use with-setter? */ for (key from 0 to max-key, state = initial-state then next-state(target, state), until: finished-state?(target, state, limit)) let val = element(coll, state, default: not-found()); unless (not-found?(val)) current-element(target, state) := fun(val) end end for end; target end method map-into-rigid-one; define method map-into-rigid-one (fun :: , target :: , coll :: ) => (target :: ); for (key from 0, val in coll) unless (not-found?(element(target, key, default: not-found()))) target[key] := fun(val) end unless end for; target end method map-into-rigid-one; // markt, some more useful copy-downs define inline copy-down-method map-into-rigid-one (fun :: , target :: , coll :: ) => (target :: ); define inline copy-down-method map-into-rigid-one (fun :: , target :: , coll :: ) => (target :: ); // Subclasses of array should have sublinear implementations of element, // so let's exploit this. define method map-into-rigid-one (fun :: , target :: , coll :: ) => (target :: ); let end-key = target.size; for (val keyed-by key in coll) unless (~instance?(key, ) | key < 0 | key >= end-key) target[key] := fun(val) end end; target end method map-into-rigid-one; define method map-into-rigid-one (fun :: , target :: , coll :: ) => (target :: ); for (val in coll, key from 0 below target.size) target[key] := fun(val) end; target end method map-into-rigid-one; define method map-into-rigid-one (fun :: , target :: , coll :: ) => (target :: ); with-fip-of coll /* Use with-setter? */ let end-key = coll.size; for (state = initial-state then next-state(target, state), until: finished-state?(target, state, limit)) let key = current-key(coll, state); unless (~instance?(key, ) | key < 0 | key >= end-key) current-element-setter(fun(coll[key]), target, state) end end end; target end method map-into-rigid-one; define method map-into-rigid-one (fun :: , target :: , coll :: ) => (target :: ); with-fip-of coll /* Use with-setter? */ for (state = initial-state then next-state(target, state), key from 0 below coll.size, until: finished-state?(target, state, limit)) current-element-setter(fun(coll[key]), target, state) end end; target end method map-into-rigid-one; // Now the case where both source and target are sequences. define method map-into-rigid-one (fun :: , target :: , coll :: ) => (target :: ); with-fip-of target /* Use with-setter? */ for (state = initial-state then next-state(target, state), val in coll, until: finished-state?(target, state, limit)) current-element-setter(fun(val), target, state); end for end; target end method map-into-rigid-one; // markt define inline copy-down-method map-into-rigid-one (fun :: , target :: , coll :: ) => (target :: ); define inline copy-down-method map-into-rigid-one (fun :: , target :: , coll :: ) => (target :: ); // And finally the case where both source and target are arrays. define method map-into-rigid-one (fun :: , target :: , coll :: ) => (target :: ); let sz = min(target.size, coll.size); without-bounds-checks for (i from 0 below min(target.size, coll.size)) target[i] := fun(coll[i]) end end; target end method map-into-rigid-one; // // ANY? // define sealed method any? (test :: , coll :: , #rest more-colls :: ) => value; select (size(more-colls)) 0 => any?-one(test, coll); 1 => any?-two(test, coll, vector-element(more-colls, 0)); otherwise => any?-one(curry(apply, test), apply(multiple-collection, coll, more-colls)); end select end method any?; define sealed generic any?-one (test :: , coll :: ) => value; define inline sealed method any?-one (test :: , coll :: ) => value; for (item in coll, result = #f then test(item), until: result) finally result end for end method any?-one; any?-one; // Silence "unused" warning. define sealed generic any?-two (test :: , c1 :: , c2 :: ) => value; define inline sealed method any?-two (test :: , c1 :: , c2 :: ) => value; for (e1 in c1, e2 in c2, result = #f then test(e1, e2), until: result) finally result end for end method any?-two; define inline sealed method any?-two (test :: , c1 :: , c2 :: ) => value; any?-one(curry(apply, test), multiple-collection(c1, c2)) end method any?-two; any?-two; // Silence "unused" warning. // // EVERY? // define sealed method every? (test :: , coll :: , #rest more-colls :: ) => value :: ; select (size(more-colls)) 0 => every?-one(test, coll); 1 => every?-two(test, coll, vector-element(more-colls, 0)); otherwise => every?-one(curry(apply, test), apply(multiple-collection, coll, more-colls)); end select end method every?; define sealed generic every?-one (test :: , coll :: ) => value :: ; define sealed inline method every?-one (test :: , coll :: ) => value :: ; for (item in coll, result = #t then test(item), while: result) finally result ~== #f end for end method every?-one; every?-one; // Silence "unused" warning. define sealed generic every?-two (test :: , c1 :: , c2 :: ) => value; define inline sealed method every?-two (test :: , c1 :: , c2 :: ) => value; for (e1 in c1, e2 in c2, result = #t then test(e1, e2), while: result) finally result ~== #f end for end method every?-two; define inline sealed method every?-two (test :: , c1 :: , c2 :: ) => value; every?-one(curry(apply, test), multiple-collection(c1, c2)) end method every?-two; every?-two; // Silence "unused" warning. // // REDUCE // define inline method reduce (fn :: , init-value, collection :: ) => (object) for (result = init-value then fn(result, item), item in collection) finally result end for end method reduce; // // REDUCE1 // define inline method reduce1 (fn :: , collection :: ) => (object) with-fip-of collection if (finished-state?(collection, initial-state, limit)) // Is there a more informative error class that's appropriate here? error(make(, format-string: "Reduce1 undefined for empty collections")) else let result = current-element(collection, initial-state); for (state = next-state(collection, initial-state) then next-state(collection, state), until: finished-state?(collection, state, limit)) result := fn(result, current-element(collection, state)); finally result end for end if end with-fip-of end method reduce1; // // MEMBER? // define method member? (value, collection :: , #key test = \==) => (boolean :: ) for (item in collection, result = #f then test(value, item), until: result) finally result & #t end for end method member?; // // FIND-KEY // define inline method find-key (collection :: , fn :: , #key skip :: = 0, failure = #f) => (key) for (e keyed-by k in collection, found = #f then fn(e) & ((skip := skip - 1) < 0), kludge = 0 then k, until: found) finally if (found) kludge else failure end if end for end method; // // Specialized inherited generic methods // // // AS // define method as (type :: , coll :: ) => (new-coll :: ) if (instance?(coll, type)) coll else map-as(type, identity, coll) end end method as; // // SHALLOW-COPY // define method shallow-copy (coll :: ) => (new-coll :: ) map(identity, coll) end method shallow-copy; // // SIZE // define method size (collection :: ) => (result :: ) for (item in collection, size :: from 0) finally size; end for end method size; // // EMPTY? // define method empty? (collection :: ) => (result :: ) with-fip-of collection finished-state?(collection, initial-state, limit) end end method empty?; // // = // // Let's start with the simple (i.e. inefficient) version, that won't work // on circular lists. define method \= (c1 :: , c2 :: ) => (eq :: ) unless (c1.key-test ~== c2.key-test | c1.size ~= c2.size) for (e1 keyed-by k in c1, eq = #t then begin let e2 = element(c2, k, default: not-found()); unless (not-found?(e2)) e1 = e2 end end, while: eq) finally eq end end end; // We now consider the two cases where one of the collections is a sequence // and the other one isn't. We assume that it will be faster to do the // random accesses on the non-sequence. define method \= (c1 :: , c2 :: ) => (eq :: ) unless (c2.key-test ~== \==) let eq = #t; for (e1 in c1, key from 0, eq = #t then begin let e2 = element(c2, key, default: not-found()); unless (not-found?(e2)) e1 = e2 end end, while: eq) finally eq & key = c2.size end for end unless end; define method \= (c1 :: , c2 :: ) => (eq :: ) c2 = c1 end; // In the two sequence case we iterate through both in parallel. define method \= (c1 :: , c2 :: ) => (eq :: ) with-fip-of c1 with prefix one- with-fip-of c2 with prefix two- iterate compare (s1 = one-initial-state, s2 = two-initial-state) case one-finished-state?(c1, s1, one-limit) => two-finished-state?(c2, s2, two-limit); two-finished-state?(c2, s2, two-limit) => #f; otherwise => one-current-element(c1, s1) = two-current-element(c2, s2) & compare(one-next-state(c1, s1), two-next-state(c2, s2)) end case end iterate end end end; // In the array case we assume that size is fast. define method \= (c1 :: , c2 :: ) => (eq :: ) unless (c1.size ~= c2.size) for (e1 in c1, e2 in c2, eq = #t then e1 = e2, while: eq) finally eq end end end; // Lists are a pain because we have to deal with the dotted case. // We are allowed to diverge if the list has a cycle. define method \= (c1 :: , c2 :: ) => (eq :: ) block (return) for (l = c1 then l.tail, key from 0) case l == #() => return (key = c2.size); ~(instance?(l, )) => return(#f); otherwise => let e = element(c2, key, default: not-found()); if (not-found?(e) | e ~= l.head) return(#f) end; end case end for end block end method \=; define inline method \= (c1 :: , c2 :: ) => (eq :: ) c2 = c1 end method \=; define method \= (c1 :: , c2 :: ) => (eq :: ) with-fip-of c2 block (return) for (l = c1 then l.tail, state = initial-state then next-state(c2, state)) case l == #() => return(finished-state?(c2, state, limit)); ( finished-state?(c2, state, limit) | ~instance?(l, ) | current-element(c2, state) ~= l.head ) => return(#f); end case end for end block end with-fip-of end method \=; define inline method \= (c1 :: , c2 :: ) => (eq :: ) c2 = c1 end method \=; // Now for the case of two lists. define sealed method \= (c1 :: , c2 :: ) => (eq :: ) c1 == c2 | (( c1.head = c2.head ) & ( c1.tail = c2.tail )) end method \=; define sealed method \= (c1 :: , c2 :: ) => (eq :: ) #t end method \=; define sealed method \= (c1 :: , c2 :: ) => (eq :: ) #f // Only called if previous two methods inapplicable end method \=; // // ELEMENT-NO-BOUNDS-CHECK // define open generic element-no-bounds-check (collection :: , key, #key default) => object; define inline method element-no-bounds-check (collection :: , key, #key default = unsupplied()) => object; element(collection, key, default: default) end method element-no-bounds-check; define function element-range-error (collection :: , key) => (will-never-return :: ) // We don't embed the collection in the condition as it will prevent the // collection having dynamic extent. A debugger should be able to display // the collection. error(make(, format-string: "ELEMENT outside of range: %=", format-arguments: list(key))) end function element-range-error; define class () end; define inline function check-nat(object) => (nat :: ) unless (check-type(object, ) >= 0) error(make(, format-string: "number >= 0 expected instead of %=", format-arguments: list(object))) end; object end; // define inline function current-element-setter // (coll :: , state, setter :: ) // => (setter :: ) // method (value) setter(value, coll, state) end // end; define constant = ; // KLUDGE FOR LIMITED COLLECTIONSXS /// define open abstract primary class ... end; // The element type for limited collections. define open generic element-type (t :: ) => type :: ; define sealed domain element-type (); define inline method element-type (t :: ) => (type == ) end method; // This function helps compute an upper bound on the maximum // integer key in a collection. define generic maximum-sequence-key(collection :: ) => key :: ;