class, prototype, meta
Ken Dickey
kend at apple.com
Mon Mar 2 18:53:36 UTC 1998
>Ken Dickey said:
>Note that I did a prototype dispatcher which did not use global tables
>(for precisely the case that network objects might have a very small
>local lifetime). The basic idea is that the selector is handed to such
>an object which does its own dispatch. When an object 'enters the
>system' it gets the selector values and creates an optimized dispatch
>function (perhaps lazily). [I can send you a page of html and a couple
>of tiffs with sample code in Scheme if you are interested].
>
>Sounds like ideal material for one of the Wiki sites. Why not send a text
>version to the mailing list and put the html and tiffs (converted to gif?)
>on one of the Wiki sites. We ought to create a discussion topic for this
>thread (if it doesn't exist already).
Er, OK. Those not interested in low level hackery can skip this. Those
who are really interested may want to slog through the expository Scheme
code at the bottom (or not 8^).
These scribblings are somewhat long, so I elided the object model
description (basically multiple inheritance by aggregation|delegation).
Cheers,
-Ken
===============================================================
FILE "NewOM"
IMPLEMENTS Description of portable oo method dispatch
technique and an object model which makes
effective use of it.
AUTHOR Ken Dickey
DATE 1995 November 6-9 -- Dispatch Technique &
expository implementation.
UPDATED 1996 March 13 -- added Object Model proposal
STATUS PRELIMINARY DRAFT; FOR REVIEW
PART I: DISPATCHING
PREFACE
Method dispatch functions typically fall into two styles: compressed
tables (e.g. [DresHoz], [HuangChen]) and generic functions with caching
(e.g. [KicRod], [HozChaUng]). The method dispatch technique described
here is less complex than the above strategies and should yield good
performance while avoiding restrictions such as global knowledge and
reliance on naming conventions. This 'locality' property makes the
technique suitable for network distributed applications.
CONTRIBUTION
The main point of this technique is not that it is faster at object
dispatch than current strategies but that it requires only local
knowledge about objects. This is important in distributed contexts where
objects may come in over the network evey few milliseconds (and live for
indeterminate times) as one does not in general wish to continually be
rebuilding global tables or updating sets of generic functions. In the
technique described, generic functions are essentially immutable,
executable data structures which may be "in-lined". A secondary
contribution of this dispatching technique is that one may use it to
build systems which use multiple interoperating object models.
CHARACTERISTICS
No global tables are required (=> can be used in network contexts)
Object model independent (can be used to support class/instance,
prototype and delegation models; single and multiple inheritance)
Not dependent on method (a.k.a. 'selector') or class names
Simple to understand and implement
DESCRIPTION
The purpose of method dispatch is to take a generic method and a set of
actual arguments and select a specific method (function) which is then
applied to these arguments. This application of specific method to
arguments is called method invocation.
In this technique, method dispatch is made up of two parts: [1] mapping
some arbitrary generic method name (a.k.a. the 'selector') to a
selection-object and [2] using this selection-object to determine which
specific method to use. The specific method is then invoked with the
generic method's actual arguments. Let us call [1] the lookup and [2] the
chooser.
The dispatch operation may be seen as {Scheme notation [Scheme]} (method
obj arg...) => (((chooser obj) (mapper method)) obj arg...) i.e. the
target object's chooser function is given the method's selection object
and yields a specific method which is applied to the generic method's
arguments. [If there exists no legal/applicable method, a suitably
crafted error method is used].
The selector-object consists of two parts: [1] a unique selector-id, and
[2] one or more bitstrings used in the second part of the dispatching
process. A new selector is created by [a] incrementing the selectorID
counter to generate a new selectorID [b] generating the bitstrings via
specialized pseudo-random number generator(s), and [c] packaging the
results into an (immutable) data structure.
CREATING THE SELECTOR
The lookup is just an environment or dictionary which maps names to
values. In the typical case a generic method is just the value of a name
in an environment where the method has lexically captured the
selector-object. E.g. Scheme code for method definition might look like
the following:
(define foo-method (make-generic-dispatcher (make-selector)))
Note that the value of foo-method is the important part, not the name. A
module system might rename the value (i.e. in another module environment,
the value of foo-method might be known by another name).
CREATING THE CHOOSER
To create the chooser for an object, the selector-object of each of its
specific methods must be known. The specific methods are stored in a
vector known as the method table for the object. There is a cutoff
parameter which is determined empirically for each machine architecture.
If the number of methods for a class/object is less that the cutoff
parameter, then a specific method is found by linear search using the
selectorID as the key (a.k.a. ribcage lookup). If the number of methods
is greater than the cutoff parameter then a hash lookup is used. The
ideal case for hashing is called a perfect hash. A perfect hash function,
when applied to its parameter, yields a different index (a.k.a. 'key')
for each method. This index is the location of the method in the method
table, where methods are placed in accord with the results of the hash
function. If no perfect hash can be found, then an imperfect hash is
used. In this case, there are indices in the lookup vector where
different hashes collide. Where index collisions occur, instead of a
method in the designated method table location, there is a hash bucket.
The hash bucket is searched linearly, again based on matching selectorIDs.
To recap, one of three strategies is used for lookup. The three
strategies used for lookup are [1] Linear Search, [2] Perfect Hash, [3]
Imperfect Hash.
There are a number of strategies which one can use to choose a hash
function and make use of the selector-object's bitstrings. One strategy
is to use several bitstrings, choose each in turn--across all
selector-objects, apply one or more hash functions and based on some set
of criteria choose the best. A sample set of criteria is: If there is a
perfect hash, use it. If there are several perfect hashes, use the one
which yields the smallest range of indices. If there is no perfect hash,
use the imperfect hash which best balances the smallest range against the
fewest collisions {the parameters are determined empirically for each
machine architecture}.
Perhaps the simplest strategy is to use one bitstring whose size fits
within a single machine register. This means that a selector-object
consists of a pair: selectorID, bitstring. The bitstring might be
generated, for example, by concatenating 4 pseudo-random numbers in the
range 0..255. The hash function used could then implemented by a
"mask-immediate and rotate" (typically 1-2 cycles on a RISC CPU). The
size of the mask accords with the size of the method table. The mask can
be "shifted" by a set amount and then tried again to yield a new hash
function. The "mask-immediate and rotate" is captured by a closure
(function) which becomes the chooser for the object/class.
E.g.
SelectorID 01001010010101010100
Mask 00000000011100000000
Shift 101
=> fn table index 5
Note that when new methods are added for an object, only the chooser need
be recalculated. Different selector-objects and choosers can be
algorithmically (and locally) generated for each machine or address space
on a network. Also, choosers may be shared by multiple objects (e.g. all
objects of the same class or prototypes having the same parents).
Finally, construction of a chooser for an object may be delayed until the
first invocation of a generic method on that object.
HOW THE SELECTOR AND CHOOSER WORK TOGETHER
A generic function is created with a unique selector-object. An object
has a set of methods which are applicable to it and used to generate a
chooser for that object. When a generic method is applied, the following
steps are followed (The first argument to the method is called the
target-object):
[1] The chooser is obtained from the target-object and applied to the
generic method's selector-object. This yields a specific method.
[2] The specific method is applied to the same arguments which were
passed to the generic method.
The chooser uses one of three strategies (as above)
[1] Linear Search: A vector of selectorIDs is searched based on the
selectorID of the selector-object. When a match is found, the index of
the matching selectorID is used as the index into the method table for
the target object to yield the specific method. (If no match is found
then an error method is returned).
[2] Perfect Hash: The chooser's hash function is applied to a bitstring
in the selector-object to yield an index. If the index is in the valid
range, it is used to index into the target object's method table and the
specific method found there is applied. (If the index is out of range,
then an error method is returned. "Holes" in the method table may be
filled with an error method).
[3] Imperfect Hash: As with [2]. If the indexed location contains a
method, it is returned, else the indexed location has yielded a "ribcage"
which is searched linearly as in [1].
PART II: Object Model
[...ELIDED...]
REFERENCES
[DresHoz] Driesen & Holzle: "Minimizing Row Displacement Dispatch
Tables", OOPSLA '95, SIGPLAN Notices 30(10),
October 1995.
[HozChaUng] Holzle, Chambers & Ungar: "Optimizing Dynamically Typed
Object-Oriented Languages with Plymorphic
Inline Caches", ECOOP '91 Conference Proceedings, Geneva, 1991.
[HuangChen] Huang & Chen: "Efficient Algorithms for Method Dispatch in
Object Oriented Programming Systems",
Journal of Object Oriented Programming, September 1992.
[KicRod] Kiczales & Rodriquez: "Efficient Method Dispatch in PCL", Proc
1990 ACM Conference on Lisp and Functional
Programming, 1990, ISBN 0-89791-386-X, ACM # 552900.
[Malef] Malefant: "On the Semantic Diversity of Delegation Based
Programming Languages", OOPSLA '95, SIGPLAN
Notices 30(10), October 1995.
[Scheme] IEEE Std 1178-1990 (IEEE/ANSI/ISO Standard for the Scheme
Programming Language), ISBN
1-55937-125-0, December 1990.
ADDENDUM: AN EXPOSITORY IMPLEMENTATION
{Scheme Programming Language [Scheme]}
;;NOTA BENE: I could not have written this in Java!
(define (MAKE-COUNTER)
(let ( (counter 0) )
(lambda ()
(set! counter (+ 1 counter))
counter)
) )
(define MAKE-SELECTOR
(let ( (new-selector-id (make-counter)) )
(lambda ()
(cons (new-selector-id) (make-random-chooser-bitstring))
) ) )
(define SELECTOR-KEY car) ;; a.k.a. selector-id
(define SELECTION-BITS cdr)
;; In a faster implementation, the following would be specialized to
;; match the number of args for each generic function and not use
;; 'rest' args.
(define (MAKE-GENERIC-DISPATCHER selector-obj)
;; return a simple dispatch function
(lambda (target-obj . args)
(apply ((chooser target-obj) selector-obj) target-obj args)
) )
(define (GENERIC-DISPATCH-ERROR obj . ignored-args)
(Error "Not a valid method for " obj)
)
(define (MAKE-RANDOM-CHOOSER-BITSTRING)
; return an integer which fits in a fixnum
(+ (random #xFF)
(lsh (random #xFF) 8)
(lsh (random #xFF) 16)
(lsh (random #x1F) 24)) ;; assume 4 byte word w 3 bit tag
)
;; 0 1 2 3
(define MASKS '#( #x000000FF #x0000FF00 #x00FF0000 #xFF000000 ) )
(define (MAKE-HASH-FUN masknum)
(let ( (mask (vector-ref masks masknum))
(shift (- (* 8 masknum)))
)
;; return a simple hash function
(lambda (fixnum)
(lsh (logAnd mask fixnum) shift)) ;; just mask & rotate
) )
(define HASH-FUNS ;; may as well share these..
(vector (make-hash-fun 0) (make-hash-fun 1) (make-hash-fun 2) )
)
;; indices 0..max-hash-fun-index
(define MAX-HASH-FUN-INDEX (- (vector-length hash-funs) 1))
(define (MASK-WIDTH-FOR max-index)
;; ceiling( log2( max-index ) )
(ceiling (/ (log max-index) (log 2)))
)
(define (MAKE-MASK mask-width)
(- (expt 2 mask-width) 1)
)
;; take a vector of selector-objs and a matching vector of specific
methods
(define (MAKE-CHOOSER selector-vec specifics-vec)
(let ( (max-index (- (vector-length selector-vec) 1)) )
(if (<= max-index linear-search-cutoff)
(make-linear-search-chooser max-index
(make-key-vector selector-vec)
specifics-vec)
(make-hash-chooser max-index selector-vec specifics-vec)
)
) )
(define (MAKE-KEY-VECTOR s-vec)
(let* ( (vec-size (vector-length s-vec))
(key-vec (make-vector vec-size 0))
(max-index (- vec-size 1))
)
(let loop ( (index 0) )
(vector-set! key-vec index
(selector-key (vector-ref s-vec index)))
(if (< index max-index) (loop (+ index 1))))
key-vec
) )
(define (MAKE-LINEAR-SEARCH-CHOOSER max-index key-rib value-rib)
(lambda (selector)
(let ( (key (selector-key selector)) )
(let loop ( (index 0) )
(cond
((eq? key (vector-ref key-rib index))
(vector-ref value-rib index)) ;; found it
((< index max-index) (loop (+ index 1))) ;; keep looking
(else generic-dispatch-error)) ;; lose, no where else to check
;; NB: delegation would replace the last clause
;; with an invocation of the parent's chooser (or
;; other search strategy).
) ) )
)
(define (MAKE-HASH-CHOOSER max-index selector-vec specifics-vec)
;; 1st, decide which hash function to use
(let* ( (mask-width (mask-width-for (vector-length selector-vec)))
(mask (make-mask mask-width))
;; gingerbread for 'multiple value' return
(details-hash-fun car)
(details-validation-vec cadr)
(details-dispatch-vec caddr)
(details
(choose-hash-details max-index
selector-vec specifics-vec
mask mask-width))
)
;; finally, return the hash-chooser
(prim-make-hash-chooser (details-hash-fun details)
(details-validation-vec details)
(details-dispatch-vec details))
) )
(define (PRIM-MAKE-HASH-CHOOSER hash-fun validation-vec dispatch-vec)
;; the validation-vec is a simple error check to weed out invalid
;; selectors (the low-level implementation can be smarter).
(let ( (dispatch-vec-size (vector-length dispatch-vec)) )
(lambda (selector)
(let ( (index (hash-fun (selection-bits selector)))
(key (selector-key selector))
)
(if (>= index dispatch-vec-size)
generic-dispatch-error ;; range error
(let ( (validation (vector-ref validation-vec index)) )
(cond
(validation
(if (eq? key validation)
(vector-ref dispatch-vec index)
generic-dispatch-error)
)
;; else validation is #f => do linear search of bucket
(else
(let ( (bucket (vector-ref dispatch-vec index)) )
(ribcage-lookup key bucket)))
) ) ; end-let
) ) ) ;; end-lambda
))
;; Again, delegation would replace the error cases
;; above with a call to a parent's invoker or other
;; lookup strategy.
(define (RIBCAGE-LOOKUP key bucket)
(let ( (max-index (- (vector-length (car bucket)) 1))
(key-vec (car bucket)) ;; Bucket is a consed ribcage.
(value-vec (cdr bucket)) ;; A single vector would be faster
) ;; (but less clear).
(let loop ( (index 0) )
(cond
((eq? key (vector-ref key-vec index))
(vector-ref value-vec index)) ;; found it
((< index max-index) (loop (+ index 1))) ;; keep looking
(else generic-dispatch-error)) ;; lose
) ) )
;; return list of hash-fun, validation-vec, dispatch-vec
;; (a.k.a. dispatch-table)
(define (CHOOSE-HASH-DETAILS max-index selector-vec specifics-vec
mask mask-width)
;; in a 'real' implementation, this might slide (shift)
;; the mask along, looking at each result for the best balance
;; of range vs # collisions. I will ignore that for now and
;; simply try 3 mask cases, ignoring the input mask.
;; This results in larger dispatch tables, but is simple and
;; easy to smarten up.
;; The basic strategy is to try each hash function in turn,
;; looking for a perfect hash (with the smallest range).
;; If no perfect hash is found, we want the smallest
;; range of imperfect hash which has a small # of collsions.
;; We also want the max number of collisions in a single bucket
;; to be less than linear-search-cutoff.
(let ( (hash0-stats
(gather-hash-stats (vector-ref hash-funs 0)
max-index selector-vec))
(hash1-stats
(gather-hash-stats (vector-ref hash-funs 1)
max-index selector-vec))
(hash2-stats
(gather-hash-stats (vector-ref hash-funs 2)
max-index selector-vec))
)
(let ( (chosen-hash-fun+stats
(choose-hash-fun-from-stats hash0-stats
hash1-stats
hash2-stats))
)
(make-v&d-vecs chosen-hash-fun+stats
selector-vec
specifics-vec)
) ) )
;; return list of hash-fun, validation-vec, dispatch-vec
(define (MAKE-V&D-VECS stats selector-vec specifics-vec)
(let* ( (base-hash-fun (stats-hash-fun stats))
(hash-base (stats-min-index stats))
(hash-fun (lambda (s) (- (base-hash-fun s) hash-base)))
(max-specifics-index (- (vector-length selector-vec) 1))
(vec-size (- (+ (stats-max-index stats) 1) hash-base))
(validation-vec (make-vector vec-size 0)) ; 0 is invalid key
(dispatch-vec (make-vector vec-size generic-dispatch-error))
(collisions? #f) ;; none so far
)
;; this fun makes a hash fun based on range min..max
;; (i.e. subtract min from hash index).
(let loop ( (from-index 0) )
(let* ( (selector-obj (vector-ref selector-vec from-index))
(to-index (hash-fun (selection-bits selector-obj)))
)
(cond
((and (number? (vector-ref validation-vec to-index))
(zero? (vector-ref validation-vec to-index)))
;; => no collisions (yet) for this location
(vector-set! validation-vec to-index
(selector-key selector-obj))
(vector-set! dispatch-vec to-index
(vector-ref specifics-vec from-index))
)
(else ;; collision for use of this vector
;; gather info for later fixup
(set! collisions? #t)
(let ( (old-val (vector-ref validation-vec to-index)) )
(vector-set!
validation-vec to-index
(cons (cons (selector-key selector-obj)
(vector-ref specifics-vec from-index))
(if (pair? old-val)
old-val
(list
(cons old-val
(vector-ref dispatch-vec
to-index))))))
)
) )
(if (< from-index max-specifics-index) (loop (+ from-index 1)))
) )
;; if imperfect hash, construct ribcage 'buckets'
(cond
(collisions?
(let loop ( (to-index 0) )
(if (pair? (vector-ref validation-vec to-index))
;; list of pairs: (selector-key specific-method)
(let* ( (to-process (vector-ref validation-vec to-index))
(rib-size (length to-process))
(keys-rib (make-vector rib-size))
(values-rib (make-vector rib-size))
(index 0)
)
(map
(lambda (pair)
(vector-set! keys-rib index (car pair))
(vector-set! values-rib index (cdr pair))
(set! index (+ index 1)))
to-process)
(vector-set! dispatch-vec to-index (cons keys-rib
values-rib))
(vector-set! validation-vec to-index #f) ;; => ribcage
) )
(if (< to-index (- vec-size 1)) (loop (+ to-index 1)))
)
) )
;; result
(list hash-fun validation-vec dispatch-vec)
) )
(define (CHOOSE-HASH-FUN-FROM-STATS . stats-list)
;; find perfect hash with smallest range
;; else use hash with least collisions
(cond
((null? stats-list) (error "No hash stats available !?!"))
((null? (cdr stats-list)) (car stats-list)) ;; wins by default
(else
(let loop ( (leader (car stats-list)) (others (cdr stats-list)) )
(if (null? others)
leader
(let ( (challanger (car others)) )
(if (stats<? leader challanger)
(loop challanger (cdr others))
(loop leader (cdr others))
) ) )
) ) )
)
(define (STATS<? stats1 stats2) ;; less implies worse, so use high scorer
(let ( (STATS-RANGE
(lambda (stats)
(- (stats-max-index stats) (stats-min-index stats))))
)
(cond
((zero? (stats-num-collisions stats2)) ; 2 has perfect hash
(if (not (zero? (stats-num-collisions stats1)))
#t ; 1 loses (is <, i.e. worse)
(> (stats-range stats1) (stats-range stats2))
))
(else ;; more collisions => worse, hence lower score
(> (stats-num-collisions stats1) (stats-num-collisions stats2)))
)
) )
(define STATS-MIN-INDEX car)
(define STATS-MAX-INDEX cadr)
(define STATS-NUM-COLLISIONS caddr)
(define STATS-HASH-FUN cadddr)
(define (GATHER-HASH-STATS hash-fun max-index selector-vec)
;; do simple insertion sort of resultant keys
;; return min & max keys (=> range) and # collisions (duplicates)
;; as (list min max collisions hash-fun)
(let ( (INSERT
(lambda (val sorted-list)
(cond
((null? sorted-list) (list val))
((> val (car sorted-list)) (cons val sorted-list))
((null? (cdr sorted-list))
(set-cdr! sorted-list (list val))
sorted-list)
(else
(let insert-loop ( (last sorted-list)
(here (cdr sorted-list))
)
(cond
((null? here)
(set-cdr! last (list val)))
((> val (car here))
(set-cdr! last (cons val here)))
(else (insert-loop here (cdr here)))
) )
sorted-list)
) )
) ; end-insert
(PROCESS-STATS
(lambda (sorted-list)
(let ( (min 0) (max 0) (collisions 0) )
(cond
((null? sorted-list)
(list min max collisions hash-fun))
((null? (cdr sorted-list))
(list (car sorted-list)
(car sorted-list) ;; min = max = this
collisions
hash-fun))
(else
(set! min (car sorted-list))
(set! max min)
(let stats-loop ( (last sorted-list)
(here (cdr sorted-list))
)
(if (null? here)
(list min max collisions hash-fun)
(let ( (val (car here)) )
(if (< val min) (set! min val))
(if (> val max) (set! max val))
(if (= val (car last))
(set! collisions (+ 1 collisions)))
(stats-loop here (cdr here))
) )
) ) )
) ) )
)
(let hash-loop ( (index 0) (sorted '()) )
(if (<= index max-index)
(hash-loop (+ index 1)
(insert (hash-fun
(selection-bits
(vector-ref selector-vec index)))
sorted))
(process-stats sorted)))
) )
;;========
;; TESTING
;;========
;; assume an object is a vector with the chooser in slot 0
(define (CHOOSER obj) (vector-ref obj 0))
(define LINEAR-SEARCH-CUTOFF 4) ;; hash if more than cutoff elts
;; A low-level implementation would have access to a generic
;; method's selector-obj, either by destructuring the cosure
;; or via a registry, but this is just test code, so we do by
;; hand what the object system would do...
;; GENERICS
(define foo-selector (make-selector))
(define foo-generic (make-generic-dispatcher foo-selector))
(define bar-selector (make-selector))
(define bar-generic (make-generic-dispatcher bar-selector))
(define bar-selector-setter (make-selector))
(define bar-generic-setter (make-generic-dispatcher
bar-selector-setter))
(define baz-selector (make-selector))
(define baz-generic (make-generic-dispatcher baz-selector))
(define glorph-selector (make-selector))
(define glorph-generic (make-generic-dispatcher glorph-selector))
(define glop-selector (make-selector))
(define glop-generic (make-generic-dispatcher glop-selector))
(define quux-selector (make-selector))
(define quux-generic (make-generic-dispatcher quux-selector))
;; OBJECT-1 SPECIALIZATIONS
(define foo-specific1 (lambda (obj arg) (+ arg 1)))
(define bar-specific1 (lambda (obj) (vector-ref obj 1)))
(define baz-specific1 (lambda (obj) 3))
(define bar-specific1-setter (lambda (obj val) (vector-set! obj 1 val)))
(define obj1-selectors
(vector foo-selector bar-selector baz-selector bar-selector-setter))
(define obj1-specifics
(vector foo-specific1 bar-specific1 baz-specific1 bar-specific1-setter))
(define obj1-chooser (make-chooser obj1-selectors obj1-specifics))
(define obj1 (vector obj1-chooser 1 2 3))
(define another-obj1 (vector obj1-chooser 'A 'B 'C))
;; OBJECT-2 SPECIALIZATIONS
(define foo-specific2 (lambda (obj arg) (+ arg 2)))(define bar-specific2
(lambda (obj) (vector-ref obj 2)))
(define bar-specific2-setter (lambda (obj val) (vector-set! obj 2 val)))
(define glorph-specific2 (lambda (obj) 3))
(define quux-specific2
(lambda (obj) (string-append (bar-generic obj) "-QUUX!!")))
(define glop-specific2 (lambda (obj) (vector-ref obj 1)))
(define obj2-selectors
(vector foo-selector bar-selector glorph-selector
bar-selector-setter quux-selector glop-selector))
(define obj2-specifics
(vector foo-specific2 bar-specific2 glorph-specific2
bar-specific2-setter quux-specific2 glop-specific2))
(define obj2-chooser (make-chooser obj2-selectors obj2-specifics))
(define obj2 (vector obj2-chooser "one" "two" "three"))
;; OBJECT-3 SPECIALIZATIONS
(define farfl-selector (make-selector))
(set-cdr! farfl-selector (cdr quux-selector)) ;; force collision
(define farfl-generic (make-generic-dispatcher farfl-selector))
(define collide-selector (make-selector))
(set-cdr! collide-selector (cdr quux-selector)) ;; force collision (again)
(define collide-generic (make-generic-dispatcher collide-selector))
(define farfl-specific3 (lambda (obj) "farfl"))
(define collide-specific3 (lambda (obj) "collide"))
(define quux-specific3 (lambda (obj) "quux"))
(define obj3-selectors
(vector foo-selector bar-selector glorph-selector
bar-selector-setter quux-selector glop-selector
farfl-selector collide-selector))
(define obj3-specifics
(vector foo-specific2 bar-specific2 glorph-specific2
bar-specific2-setter quux-specific3 glop-specific2
farfl-specific3 collide-specific3))
(define obj3-chooser (make-chooser obj3-selectors obj3-specifics))
(define obj3 (vector obj3-chooser "a" "b" "c"))
;; LINEAR SEARCH TEST CASES
(foo-generic obj1 10) ;;==> 11
(bar-generic obj1) ;;==> 1
(bar-generic-setter obj1 666) ;; Unspecified
(bar-generic obj1) ;;==> 666
(bar-generic another-obj1) ;;==> a
;(quux-generic obj1) ;; SHOULD FAIL
;; PERFECT HASH TEST CASES
(foo-generic obj2 10) ;;==> 12
(bar-generic obj2) ;;==> "two"
(bar-generic-setter obj2 "X") ;; Unspecified
(bar-generic obj2) ;;==> "X"
(quux-generic obj2) ;;==> "X-QUUX!!"
;(baz-generic obj2) ;; SHOULD FAIL
;; IMPERFECT HASH TEST CASES
(foo-generic obj3 10) ;;==> 12
(quux-generic obj3) ;;==> "quux"
(collide-generic obj3) ;;==> "collide"
(farfl-generic obj3) ;;==> "farfl"
;(baz-generic obj3) ;; SHOULD FAIL
--- E O F ---
More information about the Squeak-dev
mailing list
|