1.10 Comparators
(require rebellion/base/comparator) | package: rebellion |
A comparator is an object that compares two values and determines whether one is greater than the other, or whether they are equivalent. This comparison must respect some total ordering, meaning that for any two values x and y:
If x is less than y, then y must be greater than x. The reverse must hold true if x is greater than y.
If x is equivalent to y, then y must be equivalent to x.
If x is equal? to y, they must be equivalent.
Note that the third requirement above does not imply that all equivalent values must be equal?. For example, the real<=> comparator considers 3 and 3.0 equivalent, but (equal? 3 3.0) returns false. A comparator for which all equivalent values are also equal is said to be consistent with equality, and comparators which do not satisfy this stronger property are inconsistent with equality. All comparators defined in rebellion/base/comparator are consistent with equality unless otherwise stated.
procedure
(comparator? v) → boolean?
v : any/c
procedure
(compare comparator left right) → comparison?
comparator : comparator? left : any/c right : any/c
syntax
(compare-infix comparator-expr comparison-chain)
comparison-chain = operand-expr operator operand-expr | operand-expr operator comparison-chain operator = < | > | <= | >= | == | !=
comparator-expr : comparator?
> (compare-infix real<=> 1 < 3) #t
> (compare-infix real<=> 1 < 3 < 5) #t
> (compare-infix real<=> 100 < 3 < (error "short circuits before reaching here")) #f
procedure
(comparator-min comparator v ...+) → any/c
comparator : comparator? v : any/c
> (comparator-min real<=> 5 8) 5
> (comparator-min string<=> "foo" "bar") "bar"
procedure
(comparator-max comparator v ...+) → any/c
comparator : comparator? v : any/c
> (comparator-max real<=> 5 8) 8
> (comparator-max string<=> "foo" "bar") "foo"
1.10.1 Constructing Comparators
procedure
(comparator-of-constants constant ...) → comparator?
constant : any/c
(define size<=> (comparator-of-constants 'small 'medium 'large))
> (compare size<=> 'small 'large) #<lesser>
> (compare size<=> 'medium 'medium) #<equivalent>
> (compare size<=> 'small 'big) comparator-of-constants: contract violation
expected: one of '(small medium large)
given: 'big
procedure
(comparator-map comparator f [#:name name]) → comparator?
comparator : comparator? f : (-> any/c any/c) name : (or/c interned-symbol? #false) = #false
> (define-record-type circle (color radius)) > (define circle<=> (comparator-map real<=> circle-radius))
> (compare circle<=> (circle #:color 'green #:radius 5) (circle #:color 'blue #:radius 8)) #<lesser>
procedure
(make-comparator function [#:name name]) → comparator?
function : (-> any/c any/c comparison?) name : (or/c interned-symbol? #false) = #false
(define symbol<=> (make-comparator (λ (left right) (cond [(symbol<? left right) lesser] [(equal? left right) equivalent] [else greater])) #:name 'symbol<=>))
> (compare symbol<=> 'apple 'banana) #<lesser>
> (compare symbol<=> 'apple 'aardvark) #<greater>
procedure
(comparator-reverse comparator) → comparator?
comparator : comparator?
> (compare real<=> 2 5) #<lesser>
> (compare (comparator-reverse real<=>) 2 5) #<greater>
procedure
(comparator-chain comparator ...+ [ #:name name]) → comparator? comparator : comparator? name : (or/c interned-symbol? #false) = #false
(define-enum-type gem-type (opal ruby diamond)) (define-tuple-type gemstone (type weight)) (define gemstone-by-type<=> (comparator-map (comparator-of-constants opal ruby diamond) gemstone-type)) (define gemstone-by-weight<=> (comparator-map real<=> gemstone-weight))
> (transduce (list (gemstone diamond 3) (gemstone ruby 5) (gemstone diamond 8) (gemstone opal 2) (gemstone ruby 14) (gemstone opal 12)) (sorting (comparator-chain gemstone-by-type<=> gemstone-by-weight<=>)) #:into into-list)
(list
(gemstone #<gem-type:opal> 2)
(gemstone #<gem-type:opal> 12)
(gemstone #<gem-type:ruby> 5)
(gemstone #<gem-type:ruby> 14)
(gemstone #<gem-type:diamond> 3)
(gemstone #<gem-type:diamond> 8))
1.10.2 Predefined Comparators
value
> (compare real<=> 42 99.99) #<lesser>
> (compare real<=> 42 +inf.0) #<lesser>
> (compare real<=> 42 -inf.0) #<greater>
> (compare real<=> 42 +nan.0) real<=>: contract violation
expected: comparable-real?
given: +nan.0
in: an operand of
(comparator/c comparable-real?)
contract from:
<pkgs>/rebellion/base/comparator.rkt
blaming: top-level
(assuming the contract is correct)
at: <pkgs>/rebellion/base/comparator.rkt:25:3
Beware that this comparator is inconsistent with equality, as it ignores the exactness of the compared numbers. This is the same behavior as <, =, and >, but it means that two un-equal? numbers may compare equivalent.
> (compare real<=> 5 5.0) #<equivalent>
> (compare real<=> -0.0 0.0) #<equivalent>
> (compare real<=> +inf.0 +inf.0) #<equivalent>
procedure
(comparable-real? v) → boolean?
v : any/c
> (comparable-real? 42) #t
> (comparable-real? +inf.0) #t
> (comparable-real? +nan.0) #f
value
> (compare natural<=> 42 100) #<lesser>
> (compare natural<=> 42 0) #<greater>
> (compare natural<=> 42 42) #<equivalent>
> (compare natural<=> 42 100.0) natural<=>: contract violation
expected: natural?
given: 100.0
in: an operand of
(comparator/c natural?)
contract from:
<pkgs>/rebellion/base/comparator.rkt
blaming: top-level
(assuming the contract is correct)
at: <pkgs>/rebellion/base/comparator.rkt:26:3
> (compare natural<=> 42 -10) natural<=>: contract violation
expected: natural?
given: -10
in: an operand of
(comparator/c natural?)
contract from:
<pkgs>/rebellion/base/comparator.rkt
blaming: top-level
(assuming the contract is correct)
at: <pkgs>/rebellion/base/comparator.rkt:26:3
value
> (compare string<=> "aardvark" "zebra") #<lesser>
> (compare string<=> "aardvark" (make-string 5 #\z)) string<=>: contract violation
expected: immutable-string?
given: "zzzzz"
in: an operand of
(comparator/c immutable-string?)
contract from:
<pkgs>/rebellion/base/comparator.rkt
blaming: top-level
(assuming the contract is correct)
at: <pkgs>/rebellion/base/comparator.rkt:27:3
value
char<=> : (comparator/c char?)
value
> (compare symbol<=> 'aardvark 'zebra) #<lesser>
> (compare symbol<=> 'aardvark 'aardvark) #<equivalent>
> (compare symbol<=> 'aardvark (string->uninterned-symbol "aardvark")) #<equivalent>
> (compare interned-symbol<=> 'aardvark 'zebra) #<lesser>
> (compare interned-symbol<=> 'aardvark (gensym 'zebra)) interned-symbol<=>: contract violation
expected: interned-symbol?
given: 'zebra993257
in: an operand of
(comparator/c interned-symbol?)
contract from:
<pkgs>/rebellion/base/comparator.rkt
blaming: top-level
(assuming the contract is correct)
at: <pkgs>/rebellion/base/comparator.rkt:30:3
1.10.3 Comparison Constants
procedure
(comparison? v) → boolean?
v : any/c
value
value
value
1.10.4 Comparator Contracts
procedure
(comparator/c operand-contract) → contract?
operand-contract : contract?
(define/contract even-integer<=> (comparator/c (and/c integer? even?)) real<=>)
> (compare even-integer<=> 2 8) #<lesser>
> (compare even-integer<=> 3 8) even-integer<=>: contract violation
expected: even?
given: 3
in: an and/c case of
an operand of
(comparator/c (and/c integer? even?))
contract from: (definition even-integer<=>)
blaming: top-level
(assuming the contract is correct)
at: eval:2:0
procedure
(comparator-operand-contract comparator) → contract?
comparator : comparator?
Warning: because this function observes whether a contract is attached to comparator, it may return different results for two comparators that are otherwise equal?. The result from comparator-operand-contract should be viewed as a best-effort optimistic estimate: any input that does not satisfy the returned contract will definitely raise an error when given to the contracted comparator, but there is no guarantee that inputs that satisfy the returned contract will be accepted.
(define/contract even-integer<=> (comparator/c (and/c integer? even?)) real<=>)
> (comparator-operand-contract even-integer<=>) (and/c integer? even?)
> (value-contract even-integer<=>) (comparator/c (and/c integer? even?))
1.10.5 Comparator Chaperones and Impersonators
procedure
(comparator-impersonate comparator [ #:operand-guard operand-guard #:properties properties #:comparison-marks marks #:chaperone? chaperone?]) → comparator? comparator : comparator? operand-guard : (or/c (-> any/c any/c #false)) = #false
properties : (hash/c impersonator-property? any/c #:immutable #true) = empty-hash marks : immutable-hash? = empty-hash chaperone? : boolean? = (false? operand-guard)
If chaperone? is true, the returned impersonator is a chaperone. In that case, operand-guard must always return a value equal to the one it is given. Furthermore, any impersonators returned from operand-guard must be chaperones.
(define printing-real<=> (comparator-impersonate real<=> #:operand-guard (λ (x) (printf "Got ~a\n" x) x) #:chaperone? #true))
> (compare printing-real<=> 4 8)
Got 4
Got 8
#<lesser>
> (chaperone-of? printing-real<=> real<=>) #t