On this page:
1.1 Interface
gen:  comparable
comparable?
hash-code
secondary-hash-code
1.2 Utilities
=
/  =
!=
group-by
=/  classes
generic-set
tail
member?

1 Equivalence Relations

 (require relation/equivalence) package: Relation

A generic interface and utilities for comparing data.

By default, the built-in equivalence operator = operates on numbers specifically, while the operators eq?, eqv? and equal? are more suitable for other comparisons depending on the type of the values being compared. Additionally, there are type-specific comparison operators, for instance char=? and string=?, that may be used if the type is known.

This module provides a generic interface that overrides the standard = operator to allow its use with any comparable type and not only numbers, performing the most appropriate comparison depending on the type of the values being compared. It also supports additional parameters to express broader notions of equivalence than simple equality. You can also provide an implementation for the interface in custom types so that they can be compared using the same standard equality operator and the generic utilities available in this module.

1.1 Interface

A generic interface that represents any object that can be compared with other objects of the same type in terms of equivalence, that is, in cases where we seek to know, "are these values equal, for some definition of equality?" All built-in as well as custom types are gen:comparable. For all custom types, the implementation defers to the built-in equal?.

Examples:
> (= 1 1)

#t

> (= 1 2)

#f

> (= 1 (void))

#f

> (= "apple" "APPLE")

#f

> (= #:key string-upcase "apple" "APPLE")

#t

> (= #:key ->number "42.0" "42/1")

#t

procedure

(comparable? v)  boolean?

  v : any/c
Predicate to check if a value is comparable via the generic equivalence operators = and /=.

Examples:
> (comparable? 3)

#t

> (comparable? #\a)

#t

> (comparable? "cherry")

#t

> (comparable? (set))

#t

> (comparable? (hash))

#t

procedure

(hash-code v)  fixnum?

  v : comparable?

procedure

(secondary-hash-code v)  fixnum?

  v : comparable?
Similar to equal-hash-code and equal-secondary-hash-code, but these yield the hash code reported by the underlying operation used to perform the equality check. For example, for numbers, hash-code evaluates to the number itself, while for strings, it evaluates to the hash code reported by equal-hash-code. Likewise, for symbols, it evaluates to the hash code reported by eq-hash-code since eq? is the check employed for symbol comparisons.

Examples:
> (hash-code 3)

3

> (hash-code #\a)

150

> (hash-code 'abc)

17293296759617959

> (hash-code "cherry")

-1027447279426916491

In order to implement this interface in custom types, all that is needed is to implement the gen:equal+hash interface. gen:comparable itself should not be implemented directly, since there is never a case where both of these interfaces would need to be implemented. To avoid any possibility of conflicting notions of equality, gen:comparable simply defers to the built-in equal? for the definition of equality for custom types.

All Racket types are gen:comparable, so = may be treated as a drop-in replacement for equal?.

1.2 Utilities

The following utilities are provided which work with any type that implements the gen:comparable interface.

procedure

(= [#:key key] v ...)  boolean?

  key : (-> comparable? comparable?) = #f
  v : comparable?
True if the v’s are equal. This uses the most appropriate equality check for the type. For instance, it uses the built-in = operator for numeric data, and equal? for some other types such as structures. If a transformation is provided via the #:key argument, then this transformation is applied to the input values first, prior to performing the equality check.

Examples:
> (= 1 1 1)

#t

> (= 1 2)

#f

> (= "apple" "apple" "apple")

#t

> (= 3/2 1.5)

#t

> (= #:key string-upcase "apple" "Apple" "APPLE")

#t

> (= #:key ->number "42.0" "42/1" "42")

#t

> (= #:key ->number "42" "42.1")

#f

> (= #:key even? 12 20)

#t

> (= #:key odd? 12 20)

#t

> (= #:key (.. even? ->number) "12" "20")

#t

procedure

(/= [#:key key] v ...)  boolean?

  key : (-> comparable? comparable?) = #f
  v : comparable?

procedure

( [#:key key] v ...)  boolean?

  key : (-> comparable? comparable?) = #f
  v : comparable?

procedure

(!= [#:key key] v ...)  boolean?

  key : (-> comparable? comparable?) = #f
  v : comparable?
True if the v’s are not equal. This is simply a negation of the generic =. If a transformation is provided via the #:key argument, then it is applied to the arguments prior to comparing them.

Examples:
> ( 1 1 2)

#t

> ( 1 1)

#f

> ( "apple" "Apple")

#t

> ( 3/2 1.5)

#f

> ( #:key length "cherry" "banana" "avocado")

#t

procedure

(group-by [#:key key] vs)  (listof list?)

  key : (-> comparable? comparable?) = #f
  vs : (listof comparable?)

procedure

(=/classes [#:key key] vs)  (listof list?)

  key : (-> comparable? comparable?) = #f
  vs : (listof comparable?)
Groups input values into equivalence classes induced by the specified equivalence relation (by default, = is applied directly unless a key is specified).

Examples:
> (group-by (list 1 2 1))

'((1 1) (2))

> (group-by (list 1 2 3))

'((1) (2) (3))

> (group-by (list 1 1 1))

'((1 1 1))

> (group-by (list 1 1 2 2 3 3 3))

'((1 1) (2 2) (3 3 3))

> (group-by (list "cherry" "banana" "apple"))

'(("cherry") ("banana") ("apple"))

> (group-by #:key length (list "apple" "banana" "cherry"))

'(("apple") ("banana" "cherry"))

procedure

(generic-set [#:key key] v ...)  list?

  key : (-> comparable? comparable?) = #f
  v : comparable?
Returns a set containing deduplicated input values using the provided equivalence relation as the test for equality (by default, = is applied directly unless a key is specified).

Examples:
> (generic-set 1 2 3)

(gset '(1 2 3) #f)

> (generic-set 1 1 2 2 3 3 3)

(gset '(1 2 3) #f)

> (generic-set "cherry" "banana" "apple")

(gset '("cherry" "banana" "apple") #f)

> (generic-set #:key odd? 1 2 3 4 5)

(gset '(1 2) #<procedure:odd?>)

> (generic-set #:key string-upcase "apple" "Apple" "APPLE" "banana" "Banana" "cherry")

(gset '("apple" "banana" "cherry") #<procedure:string-upcase>)

> (define my-set (generic-set #:key string-upcase "cherry" "banana" "apple"))
> (set-add my-set "APPLE")

(gset '("APPLE" "cherry" "banana") #<procedure:string-upcase>)

procedure

(tail [#:key key] elem col)  sequence?

  key : (-> comparable? comparable?) = #f
  elem : comparable?
  col : sequence?
A generic version of member that operates on any sequence rather than lists specifically, and employs the generic = relation rather than the built-in equal?. The result of invocation is the tail of the sequence beginning at the value that is = to elem, under the transformation key (if provided), or the empty list if elem isn’t found. If a boolean value is desired, use member? instead. If a tail by position is desired, use drop.

Examples:
> (tail 4 (list 1 2 3))

'()

> (tail 4 (list 1 4 3))

'(4 3)

> (->list (tail "cherry" (stream "apple" "banana" "cherry")))

'("cherry")

> (tail "BANANA" (list "apple" "banana" "cherry"))

'()

> (tail #:key string-upcase "BANANA" (list "apple" "banana" "cherry"))

'("banana" "cherry")

procedure

(member? [#:key key] elem col)  boolean?

  key : (-> comparable? comparable?) = #f
  elem : comparable?
  col : sequence?
A generic version of member similar to tail that checks if a value is present in a collection. Unlike tail, this returns a boolean value and supports non-ordered collections like sets where a tail is not well-defined. In the special case where col is a generic-set, the key provided to member?, if any, is ignored as it may conflict with the existing key defining the equivalence relation in the generic set.

Examples:
> (member? 4 (list 1 2 3))

#f

> (member? 4 (list 1 4 3))

#t

> (member? "cherry" (stream "apple" "banana" "cherry"))

#t

> (member? "BANANA" (list "apple" "banana" "cherry"))

#f

> (member? #:key string-upcase "BANANA" (list "apple" "banana" "cherry"))

#t

> (member? "BANANA" (generic-set #:key string-upcase "apple" "banana" "cherry"))

#t

> (member? "tomato" (generic-set #:key length "apple" "banana" "grape"))

#t