동등성

동등성 (Equality)

동등성(equality)은 두 값이 "같은(the same)" 것인지에 대한 개념이에요. Racket은 기본적으로 몇 가지 서로 다른 종류의 동등성을 지원하는데, 대부분의 용도에서는 equal?가 선호됩니다.

출처: Racket Reference

본문

procedure
(equal? v1 v2) → boolean?
  v1 : any/c
  v2 : any/c

특정 데이터 타입에 대해 다르게 지정되지 않는 한, 두 값은 eqv?일 때만 equal?입니다.

equal?에 대한 추가 지정이 있는 데이터 타입에는 문자열, 바이트 문자열, 페어, 변경 가능 페어, 벡터, 박스, 해시 테이블, 그리고 검사 가능한(inspectable) 구조체가 있습니다. 마지막 여섯 경우에서 동등성은 재귀적으로 정의됩니다. v1v2 둘 다 참조 사이클을 포함하면, 값들의 무한 펼침(infinite unfoldings)이 같을 때 그들은 같습니다. gen:equal+hashprop:impersonator-of도 참고하세요.

예시:

> (equal? 'yes 'yes)
#t
> (equal? 'yes 'no)
#f
> (equal? (* 6 7) 42)
#t
> (equal? (expt 2 100) (expt 2 100))
#t
> (equal? 2 2.0)
#f
> (let ([v (mcons 1 2)]) (equal? v v))
#t
> (equal? (mcons 1 2) (mcons 1 2))
#t
> (equal? (integer->char 955) (integer->char 955))
#t
> (equal? (make-string 3 #\z) (make-string 3 #\z))
#t
> (equal? #t #t)
#t

사용자 정의 구조체 타입에 대한 동등성은 "Opaque versus Transparent Structure Types"를 참고하세요.

procedure
(equal-always? v1 v2) → boolean?
  v1 : any/c
  v2 : any/c

v1v2가 같고, 변경(mutation)과 무관하게 항상 같게 유지될 것인지 여부를 나타냅니다. 일반적으로, 두 값이 equal-always가 되려면, v1v2 안의 대응하는 불변(immutable) 값들은 equal?여야 하고, 대응하는 변경 가능(mutable) 값들은 eq?여야 합니다. 다른 언어에서 이 연산자에 대한 선례는 egal [Baker93]입니다.

두 값 v1v2는, v1v2가 모두 v3의 챠페론(chaperone)인 세 번째 값 v3이 존재할 때만 equal-always?입니다. 즉 (chaperone-of? v1 v3)(chaperone-of? v2 v3)가 모두 참이라는 뜻입니다.

챠페론이나 다른 impersonator를 포함하지 않는 값들에 대해, v1v2는 대응하는 변경 가능 벡터, 박스, 해시 테이블, 문자열, 바이트 문자열, 변경 가능 페어, 그리고 v1v2 안의 변경 가능 구조체가 eq?여야 하고, 구조체에 대한 동등성이 gen:equal-mode+hash를 통해 equal-always?용으로 특수화될 수 있다는 점을 제외하면, equal?일 때 equal-always로 간주될 수 있습니다.

예시:

> (equal-always? 'yes 'yes)
#t
> (equal-always? 'yes 'no)
#f
> (equal-always? (* 6 7) 42)
#t
> (equal-always? (expt 2 100) (expt 2 100))
#t
> (equal-always? 2 2.0)
#f
> (equal-always? (list 1 2) (list 1 2))
#t
> (let ([v (mcons 1 2)]) (equal-always? v v))
#t
> (equal-always? (mcons 1 2) (mcons 1 2))
#f
> (equal-always? (integer->char 955) (integer->char 955))
#t
> (equal-always? (make-string 3 #\z) (make-string 3 #\z))
#f
> (equal-always? (string->immutable-string (make-string 3 #\z))
                 (string->immutable-string (make-string 3 #\z)))
#t
> (equal-always? #t #t)
#t

base 패키지의 8.5.0.3 버전에서 추가됨.

procedure
(eqv? v1 v2) → boolean?
  v1 : any/c
  v2 : any/c

특정 데이터 타입에 대해 다르게 지정되지 않는 한, 두 값은 eq?일 때만 eqv?입니다.

숫자 데이터 타입만 eqv?eq?와 다른 유일한 경우입니다. 두 숫자는 같은 정확성(exactness)과 정밀도(precision)를 갖고, 둘 다 같고 0이 아니거나, 둘 다 +0.0, 둘 다 +0.0f0, 둘 다 -0.0, 둘 다 -0.0f0, 둘 다 +nan.0, 또는 둘 다 +nan.f일 때 — 복소수의 경우 실수부와 허수부를 따로 고려해서 — eqv?입니다.

일반적으로 eqv?equal?와 동일하지만, 전자는 (리스트와 구조체 같은) 복합 데이터 타입의 내용을 재귀적으로 비교할 수 없고 사용자 정의 데이터 타입으로 사용자 지정할 수 없습니다. eqv?의 사용은 equal?를 선호하는 쪽으로 살짝 권장되지 않습니다.

예시:

> (eqv? 'yes 'yes)
#t
> (eqv? 'yes 'no)
#f
> (eqv? (* 6 7) 42)
#t
> (eqv? (expt 2 100) (expt 2 100))
#t
> (eqv? 2 2.0)
#f
> (let ([v (mcons 1 2)]) (eqv? v v))
#t
> (eqv? (mcons 1 2) (mcons 1 2))
#f
> (eqv? (integer->char 955) (integer->char 955))
#t
> (eqv? (make-string 3 #\z) (make-string 3 #\z))
#f
> (eqv? #t #t)
#t

base 패키지의 9.0.0.10 버전에서 변경됨: 문자에 대해 equal?eqv?뿐만 아니라 eq?를 함의합니다.

procedure
(eq? v1 v2) → boolean?
  v1 : any/c
  v2 : any/c

v1v2가 같은 객체를 가리키면 #t, 그렇지 않으면 #f를 반환합니다. 숫자 사이의 특별한 경우로, =인 두 fixnum은 eq?에 따라서도 같습니다. "Object Identity and Comparisons"도 참고하세요.

예시:

> (eq? 'yes 'yes)
#t
> (eq? 'yes 'no)
#f
> (eq? (* 6 7) 42)
#t
> (eq? (expt 2 100) (expt 2 100))
#f
> (eq? 2 2.0)
#f
> (let ([v (mcons 1 2)]) (eq? v v))
#t
> (eq? (mcons 1 2) (mcons 1 2))
#f
> (eq? (integer->char 955) (integer->char 955))
#t
> (eq? (make-string 3 #\z) (make-string 3 #\z))
#f
> (eq? #t #t)
#t
procedure
(equal?/recur v1 v2 recur-proc) → boolean?
  v1 : any/c
  v2 : any/c
  recur-proc : (any/c any/c . -> . any/c)

equal?와 같지만 재귀 비교에 recur-proc를 사용합니다(즉 참조 사이클이 자동으로 처리되지 않습니다). recur-proc#f가 아닌 결과들은 equal?/recur가 반환하기 전에 #t로 변환됩니다.

예시:

> (equal?/recur 1 1 (lambda (a b) #f))
#t
> (equal?/recur '(1) '(1) (lambda (a b) #f))
#f
> (equal?/recur '#(1 1 1) '#(1 1.2 3/4)
              (lambda (a b) (<= (abs (- a b)) 0.25)))
#t
procedure
(equal-always?/recur v1 v2 recur-proc) → boolean?
  v1 : any/c
  v2 : any/c
  recur-proc : (any/c any/c . -> . any/c)

equal-always?와 같지만 재귀 비교에 recur-proc를 사용합니다(즉 참조 사이클이 자동으로 처리되지 않습니다). recur-proc#f가 아닌 결과들은 equal-always?/recur가 반환하기 전에 #t로 변환됩니다.

예시:

> (equal-always?/recur 1 1 (lambda (a b) #f))
#t
> (equal-always?/recur '(1) '(1) (lambda (a b) #f))
#f
> (equal-always?/recur (vector-immutable 1 1 1) (vector-immutable 1 1.2 3/4)
                       (lambda (a b) (<= (abs (- a b)) 0.25)))
#t

객체 정체성과 비교 (Object Identity and Comparisons)

eq? 연산자는 두 값을 비교해서, 값들이 같은 객체를 가리킬 때 #t를 반환합니다. 이런 형태의 동등성은 명령형 갱신(imperative update)을 지원하는 객체를 비교하는 데 적합합니다(예: 한 참조를 통한 객체 수정의 효과가 다른 참조를 통해서도 보이는지 결정). 또한 eq? 테스트는 빠르게 평가되며, 해시 테이블에서 eq? 기반 해싱은 equal? 기반 해싱보다 더 가볍습니다.

그러나 어떤 경우에는 eq?가 비교 연산자로 부적합한데, 객체 생성이 명확하게 정의되지 않기 때문입니다. 특히, 같은 두 정확한 정수에 대한 두 번의 + 적용은 결과가 항상 equal?이지만 eq?일 수도 있고 아닐 수도 있습니다. 마찬가지로 lambda 폼의 평가는 보통 새 프로시저 객체를 생성하지만, 같은 소스 lambda 폼이 이전에 생성한 프로시저 객체를 재사용할 수도 있습니다.

eq?에 대한 데이터 타입의 동작은 일반적으로 그 데이터 타입과 관련 프로시저들과 함께 지정됩니다.

동등성과 해싱 (Equality and Hashing)

비교 가능한 모든 값은 적어도 하나의 해시 코드를 갖습니다 — 값에 해시 함수를 적용해 계산한 임의의 정수(더 정확히는 fixnum)입니다. 이러한 해시 코드의 정의적 속성은 같은 값들이 같은 해시 코드를 갖는다는 것입니다. 역은 참이 아닙니다: 같지 않은 두 값도 같은 해시 코드를 가질 수 있어요. 해시 코드는 다양한 인덱싱과 비교 연산에 유용하며, 특히 해시 테이블의 구현에서 유용합니다. 자세한 내용은 "Hash Tables"를 참고하세요.

procedure
(equal-hash-code v) → fixnum?
  v : any/c

equal?와 일관된 해시 코드를 반환합니다. equal? 값들로 이루어진 두 호출에 대해 반환된 숫자는 같습니다. v가 페어, 벡터, 박스, 그리고/또는 검사 가능한 구조체 필드를 통해 사이클을 포함하더라도 해시 코드가 계산됩니다. 추가로, 사용자 정의 데이터 타입은 gen:equal+hashgen:equal-mode+hash를 구현함으로써 이 해시 코드가 계산되는 방식을 사용자 지정할 수 있습니다.

read로 만들어질 수 있는 어떤 v에 대해, v2가 같은 입력 문자들로 read에 의해 만들어진다면, (equal-hash-code v)(equal-hash-code v2)와 같습니다 — vv2가 같은 시간에 존재하지 않더라도(따라서 equal?를 호출해 비교할 수 없더라도) 말이죠.

base 패키지의 6.4.0.12 버전에서 변경됨: 읽을 수 있는(readable) 값에 대한 보장이 강화되었습니다.

procedure
(equal-hash-code/recur v recur-proc) → fixnum?
  v : any/c
  recur-proc : (-> any/c exact-integer?)

equal-hash-code와 같지만 v 안의 재귀 해싱에 recur-proc를 사용합니다.

예시:

> (define (rational-hash x)
    (cond
      [(rational? x) (equal-hash-code (inexact->exact x))]
      [else (equal-hash-code/recur x rational-hash)]))
> (= (rational-hash 0.0) (rational-hash -0.0))
#t
> (= (rational-hash 1.0) (rational-hash -1.0))
#f
> (= (rational-hash (list (list (list 4.0 0.0) 9.0) 6.0))
     (rational-hash (list (list (list 4 0) 9) 6)))
#t

base 패키지의 8.8.0.9 버전에서 추가됨.

procedure
(equal-secondary-hash-code v) → fixnum?
  v : any/c

equal-hash-code와 같지만 이중 해싱(double hashing)에 적합한 보조 해시 코드를 계산합니다.

procedure
(equal-always-hash-code v) → fixnum?
  v : any/c

equal-always?와 일관된 해시 코드를 반환합니다. equal-always? 값들로 이루어진 두 호출에 대해 반환된 숫자는 같습니다.

equal-always-hash-codev를 순회할 때, v 안의 불변 값들은 equal-hash-code로 해시되고, v 안의 변경 가능 값들은 eq-hash-code로 해시됩니다.

procedure
(equal-always-hash-code/recur v recur-proc) → fixnum?
  v : any/c
  recur-proc : (-> any/c exact-integer?)

equal-always-hash-code와 같지만 v 안의 재귀 해싱에 recur-proc를 사용합니다.

base 패키지의 8.8.0.9 버전에서 추가됨.

procedure
(equal-always-secondary-hash-code v) → fixnum?
  v : any/c

equal-always-hash-code와 같지만 이중 해싱에 적합한 보조 해시 코드를 계산합니다.

procedure
(eq-hash-code v) → fixnum?
  v : any/c

eq?와 일관된 해시 코드를 반환합니다. eq? 값들로 이루어진 두 호출에 대해 반환된 숫자는 같습니다.

같은 fixnum들은 항상 eq?입니다.

procedure
(eqv-hash-code v) → fixnum?
  v : any/c

eqv?와 일관된 해시 코드를 반환합니다. eqv? 값들로 이루어진 두 호출에 대해 반환된 숫자는 같습니다.

사용자 정의 타입에 대한 동등성 구현 (Implementing Equality for Custom Types)

value
gen:equal+hash : any/c

equal?를 사용해 동등성을 비교할 수 있는 타입들을 위한 제네릭 인터페이스("Generic Interfaces" 참고)입니다. 다음 메서드들이 구현되어야 합니다:

  • equal-proc : (-> any/c any/c (-> any/c any/c boolean?) any/c) — 처음 두 인자가 같은지 테스트합니다. 여기서 두 값 모두 제네릭 인터페이스가 연관된 구조체 타입(또는 그 구조체 타입의 하위 타입)의 인스턴스입니다.

    세 번째 인자는 재귀 동등성 검사에 사용할 equal? 술어입니다. 데이터 사이클이 제대로 처리되고 equal?/recur와 함께 작동하도록 주어진 술어를 equal? 대신 사용하세요(단, 재귀 검사에 임의의 함수가 equal?/recur에 제공될 수 있으므로, 술어에 제공되는 인자들이 임의의 코드에 노출될 수 있음을 주의하세요).

    equal-proc는 두 구조체가 eq?가 아닐 때만, 그리고 둘 다 같은 구조체 타입에서 상속된 gen:equal+hash 값을 가질 때만 구조체 쌍에 대해 호출됩니다. 이 전략을 통해 equal?가 두 구조체를 받는 순서는 중요하지 않습니다. 또한 기본적으로 구조체 하위 타입은 있다면 부모의 동등성 술어를 상속한다는 뜻입니다.

  • hash-proc : (-> any/c (-> any/c exact-integer?) exact-integer?)equal-hash-code처럼 주어진 구조체에 대한 해시 코드를 계산합니다. 첫 번째 인자는 제네릭 인터페이스가 연관된 구조체 타입(또는 그 하위 타입 중 하나)의 인스턴스입니다.

    두 번째 인자는 재귀 해시 코드 계산에 사용할 equal-hash-code류의 프로시저입니다. 데이터 사이클이 제대로 처리되도록 주어진 프로시저를 equal-hash-code 대신 사용하세요.

    hash-proc의 결과는 어떤 정확한 정수라도 될 수 있지만, 대부분의 목적(예: equal-hash-code의 결과)에서 fixnum으로 잘립니다. 대략, 잘림은 비트별 AND를 사용해 숫자의 낮은 비트들을 취합니다. 따라서 해시 코드 계산의 변동은 hash-proc 결과의 fixnum 호환 비트들에 반영되어야 합니다. 해시 코드 소비자는 fixnum 범위 안에서 변동을 적절히 사용할 것으로 기대되며, 생산자는 fixnum에 들어맞는 비트 전체 범위에 걸쳐 해시 코드 변동을 반영할 책임이 없습니다.

  • hash2-proc : (-> any/c (-> any/c exact-integer?) exact-integer?) — 주어진 구조체에 대한 보조 해시 코드를 계산합니다. 이 프로시저는 hash-proc와 같지만, equal-secondary-hash-code에 해당합니다.

hash-prochash2-procequal-proc와 일관되도록 주의하세요. 구체적으로, equal-proc가 참 값을 만드는 두 구조체에 대해 hash-prochash2-proc는 같은 값을 만들어야 합니다.

equal-procequal?에만 사용되는 것이 아니라 equal?/recur, impersonator-of?에도 사용됩니다. 게다가 구조체 타입에 변경 가능 필드가 없으면, equal-procequal-always?chaperone-of?에도 사용됩니다. 마찬가지로 hash-prochash2-proc는 구조체 타입에 변경 가능 필드가 없을 때 각각 equal-always-hash-codeequal-always-secondary-hash-code에 사용됩니다. 이 메서드들의 인스턴스는 "Honest Custom Equality"의 지침을 따라 이 모든 연산을 합리적으로 구현해야 합니다. 특히, 이 메서드들은 구조체가 mutable로 선언되지 않는 한 변경 가능 데이터에 접근해서는 안 됩니다.

구조체 타입에 gen:equal+hashgen:equal-mode+hash 구현이 없으면, 투명한(transparent) 구조체(즉, 현재 인스펙터가 제어하는 인스펙터를 가진 구조체)는 같은 구조체 타입(하위 타입은 제외)의 인스턴스이고 equal? 필드 값을 가질 때 equal?입니다. 투명한 구조체에 대해, equal-hash-codeequal-secondary-hash-code (변경 가능 필드가 없는 경우)는 필드 값들을 사용해 해시 코드를 도출합니다. 적어도 하나의 변경 가능 필드를 가진 투명한 구조체 타입에 대해, equal-always?eq?와 같고, equal-secondary-hash-code 결과는 eq-hash-code에만 기반합니다. 불투명한 구조체 타입에 대해, equal?eq?와 같고, equal-hash-codeequal-secondary-hash-code 결과는 eq-hash-code에만 기반합니다. 구조체가 prop:impersonator-of 속성을 가지면, 속성 값의 프로시저가 구조체에 적용될 때 #f가 아닌 값을 반환하면 prop:impersonator-of 속성이 gen:equal+hash보다 우선합니다.

예시:

(define (farm=? farm1 farm2 recursive-equal?)
  (and (= (farm-apples farm1)
          (farm-apples farm2))
       (= (farm-oranges farm1)
          (farm-oranges farm2))
       (= (farm-sheep farm1)
          (farm-sheep farm2))))

(define (farm-hash-code farm recursive-equal-hash)
  (+ (* 10000 (farm-apples farm))
     (* 100 (farm-oranges farm))
     (* 1 (farm-sheep farm))))

(define (farm-secondary-hash-code farm recursive-equal-hash)
  (+ (* 10000 (farm-sheep farm))
     (* 100 (farm-apples farm))
     (* 1 (farm-oranges farm))))

(struct farm (apples oranges sheep)
  #:methods gen:equal+hash
  [(define (equal-proc farm1 farm2 recursive-equal?)
     (farm=? farm1 farm2 recursive-equal?))
   (define (hash-proc farm recursive-equal-hash)
     (farm-hash-code farm recursive-equal-hash))
   (define (hash2-proc farm recursive-equal-hash)
     (farm-secondary-hash-code farm recursive-equal-hash))])

(define eastern-farm (farm 5 2 20))
(define western-farm (farm 18 6 14))
(define northern-farm (farm 5 20 20))
(define southern-farm (farm 18 6 14))

> (equal? eastern-farm western-farm)
#f
> (equal? eastern-farm northern-farm)
#f
> (equal? western-farm southern-farm)
#t

base 패키지의 8.7.0.5 버전에서 변경됨: equal-proc, hash-proc, hash2-proc 중 어느 것을 생략해도 이제 구문 오류가 되도록 검사가 추가되었습니다.

value
gen:equal-mode+hash : any/c

equal?equal-always? 사이의 차이를 지정할 수 있는 타입들을 위한 제네릭 인터페이스("Generic Interfaces" 참고)입니다. 다음 메서드들이 구현되어야 합니다:

  • equal-mode-proc : (-> any/c any/c (-> any/c any/c boolean?) boolean? any/c) — 처음 두 인자는 비교할 값들이고, 세 번째 인자는 재귀 비교에 사용할 동등성 함수이며, 마지막 인자는 모드(mode)입니다: equal?impersonator-of? 비교이면 #t, equal-always?chaperone-of? 비교이면 #f입니다.
  • hash-mode-proc : (-> any/c (-> any/c exact-integer?) boolean? exact-integer?) — 첫 번째 인자는 해시 코드를 계산할 값이고, 두 번째 인자는 재귀 해싱에 사용할 해싱 함수이며, 마지막 인자는 모드입니다: equal? 해싱이면 #t, equal-always? 해싱이면 #f입니다.

hash-mode-proc 구현은 기본 해시 코드와 보조 해시 코드 둘 다에 사용됩니다.

이 메서드들을 구현할 때 "Honest Custom Equality"의 지침을 따르세요. 특히, 이 메서드들은 "mode" 인자가 equal?impersonator-of?를 나타내는 참일 때만 변경 가능 데이터에 접근해야 합니다.

gen:equal-mode+hash를 구현하는 것은 equal?equal-always? 사이의 차이를 지정하는 타입(예: 변경 가능 데이터를 getter와 setter 프로시저로 감싸는 구조체 타입)에 가장 유용합니다:

예시:

> (define (get gs) ((getset-getter gs)))
> (define (set gs new) ((getset-setter gs) new))

> (struct getset (getter setter)
    #:methods gen:equal-mode+hash
    [(define (equal-mode-proc self other rec mode)
       (and mode (rec (get self) (get other))))
     (define (hash-mode-proc self rec mode)
       (if mode (rec (get self)) (eq-hash-code self)))])
> (define x 1)
> (define y 2)
> (define gsx (getset (lambda () x) (lambda (new) (set! x new))))
> (define gsy (getset (lambda () y) (lambda (new) (set! y new))))
> (equal? gsx gsy)
#f
> (equal-always? gsx gsy)
#f
> (set gsx 3)
> (set gsy 3)
> (equal? gsx gsy)
#t
> (equal-always? gsx gsy)
#f
> (equal-always? gsx gsx)
#t

base 패키지의 8.5.0.3 버전에서 추가됨. 8.7.0.5 버전에서 변경됨: equal-mode-prochash-mode-proc 중 어느 것을 생략해도 이제 구문 오류가 되도록 검사가 추가되었습니다.

value
prop:equal+hash : struct-type-property?

구조체 타입에 동등성 술어와 해싱 함수를 공급하는 구조체 타입 속성("Structure Type Properties" 참고)입니다. prop:equal+hash 속성을 사용하는 것은 gen:equal+hashgen:equal-mode+hash 제네릭 인터페이스를 사용하는 것의 대안입니다.

prop:equal+hash 속성 값은 세 개의 프로시저 리스트 (list equal-proc hash-proc hash2-proc) 또는 두 개의 프로시저 리스트 (list equal-mode-proc hash-mode-proc)입니다:

  • 세 프로시저 경우는 gen:equal+hash의 프로시저들에 해당합니다:
    • equal-proc : (-> any/c any/c (-> any/c any/c boolean?) any/c)
    • hash-proc : (-> any/c (-> any/c exact-integer?) exact-integer?)
    • hash2-proc : (-> any/c (-> any/c exact-integer?) exact-integer?)
  • 두 프로시저 경우는 gen:equal-mode+hash의 프로시저들에 해당합니다:
    • equal-mode-proc : (-> any/c any/c (-> any/c any/c boolean?) boolean? any/c)
    • hash-mode-proc : (-> any/c (-> any/c exact-integer?) boolean? exact-integer?)

이 메서드들을 구현할 때 "Honest Custom Equality"의 지침을 따르세요. 특히, 이 메서드들은 구조체가 mutable로 선언되거나 mode가 참일 때만 변경 가능 데이터에 접근해야 합니다.

base 패키지의 8.5.0.3 버전에서 변경됨: equal-always?를 사용자 지정하기 위한 두 프로시저 값 지원이 추가되었습니다.

정직한 사용자 정의 동등성 (Honest Custom Equality)

equal-procequal-mode-procequal?보다 더 많은 것에 사용되므로, 그것들의 인스턴스는 equal-always?, chaperone-of?, impersonator-of?에 대해 올바르게 작동하도록 특정 지침을 따라야 합니다.

이 연산들의 차이 때문에, 그것들 안에서 equal?를 호출하는 것을 피하세요. 대신 세 번째 인자를 사용해 조각들에 대해 "재귀(recur)"하세요. 이것은 equal?/recur가 제대로 작동하게 하고, 다른 연산들이 조각들에 대해 각자의 고유한 방식으로 동작하게 하며, 일부 사이클 탐지를 가능하게 합니다.

좋음(good):

(define (equal-proc self other rec)
  (rec (fish-size self) (fish-size other)))

나쁨(bad):

(define (equal-proc self other rec)
  (equal? (fish-size self) (fish-size other)))

세 번째 인자를 요소 "개수(counts)"에 재귀하는 데 사용하지 마세요. 데이터 구조가 이산(discrete) 숫자에 관심을 가질 때, 그것들에 equal?나 "재귀" 대신 =를 사용할 수 있습니다. "재귀" 인자가 서로 어떤 범위 안에서 수치적으로 너무 관대할 때 개수에 "재귀"를 사용하는 것은 나쁩니다.

좋음:

(define (equal-proc self other rec)
  (and (= (tuple-length self) (tuple-length other))
       (for/and ([i (in-range (tuple-length self))])
         (rec ((tuple-getter self) i)
              ((tuple-getter other) i)))))

나쁨:

(define (equal-proc self other rec)
  (and (rec (tuple-length self) (tuple-length other))
       (for/and ([i (in-range (tuple-length self))])
         (rec ((tuple-getter self) i)
              ((tuple-getter other) i)))))

equal?equal-always? 연산은 대칭적이어야 하므로, equal-proc 인스턴스는 인자가 뒤바뀌어도 답이 바뀌면 안 됩니다:

좋음:

(define (equal-proc self other rec)
  (rec (fish-size self) (fish-size other)))

나쁨:

(define (equal-proc self other rec)
  (<= (fish-size self) (fish-size other)))

그러나 chaperone-of?impersonator-of? 연산은 대칭적이지 않으므로, 세 번째 인자로 조각들에 "재귀"할 때는 조각들을 들어온 순서 그대로 전달하세요:

좋음:

(define (equal-proc self other rec)
  (rec (fish-size self) (fish-size other)))

나쁨:

(define (equal-proc self other rec)
  (rec (fish-size other) (fish-size self)))

equal-always?chaperone-of? 연산은 변경에 따라 바뀌지 않아야 하므로, equal-proc 인스턴스는 잠재적으로 변경 가능한 데이터에 접근하면 안 됩니다. 여기에는 문자열이 변경 가능할 수 있으므로 string=?를 피하는 것이 포함됩니다. symbol=? 같은 불변 타입을 위한 타입 특정 동등성 함수는 괜찮습니다.

괜찮음(fine):

(define (equal-proc self other rec)
  ; symbols are immutable: no problem
  (symbol=? (thing-name self) (thing-name other)))

나쁨:

(define (equal-proc self other rec)
  ; strings can be mutable: accesses mutable data
  (string=? (thing-name self) (thing-name other)))

구조체를 mutable로 선언하면 equal-always?chaperone-of?equal-proc 사용을 피하게 만드므로, 구조체가 mutable로 선언되면 equal-proc 인스턴스는 변경 가능 데이터에 자유롭게 접근할 수 있습니다:

좋음:

(struct mcell (value) #:mutable
  #:methods gen:equal+hash
  [(define (equal-proc self other rec)
     (rec (mcell-value self)
          (mcell-value other)))
   (define (hash-proc self rec)
     (+ (eq-hash-code struct:mcell)
        (rec (mcell-value self))))
   (define (hash2-proc self rec)
     (+ (eq-hash-code struct:mcell)
        (rec (mcell-value self))))])

나쁨:

(struct mcell (box)
  ; not declared mutable,
  ; but represents mutable data anyway
  #:methods gen:equal+hash
  [(define (equal-proc self other rec)
     (rec (unbox (mcell-box self))
          (unbox (mcell-box other))))
   (define (hash-proc self rec)
     (+ (eq-hash-code struct:mcell)
        (rec (unbox (mcell-value self)))))
   (define (hash2-proc self rec)
     (+ (eq-hash-code struct:mcell)
        (rec (unbox (mcell-value self)))))])

구조체가 변경 가능 데이터에 대한 접근을 제어하는 또 다른 방법은 gen:equal+hash 대신 gen:equal-mode+hash를 구현하는 것입니다. mode가 참일 때 equal-mode-proc 인스턴스는 변경 가능 데이터에 자유롭게 접근할 수 있고, mode가 거짓일 때는 접근하면 안 됩니다:

또한 좋음(also good):

(struct mcell (value) #:mutable
  ; only accesses mutable data when mode is true
  #:methods gen:equal-mode+hash
  [(define (equal-mode-proc self other rec mode)
     (and mode
          (rec (mcell-value self)
               (mcell-value other))))
   (define (hash-mode-proc self rec mode)
     (if mode
         (+ (eq-hash-code struct:mcell)
            (rec (mcell-value self)))
         (eq-hash-code self)))])

여전히 나쁨(still bad):

(struct mcell (value) #:mutable
  ; accesses mutable data ignoring mode
  #:methods gen:equal-mode+hash
  [(define (equal-mode-proc self other rec mode)
     (rec (mcell-value self)
          (mcell-value other)))
   (define (hash-mode-proc self rec mode)
     (+ (eq-hash-code struct:mcell)
        (rec (mcell-value self))))])

해시 코드 결합 (Combining Hash Codes)

(require racket/hash-code) package: base

이 섹션에 문서화된 바인딩들은 racket/baseracket이 아니라 racket/hash-code 라이브러리가 제공합니다.

base 패키지의 8.8.0.5 버전에서 추가됨.

procedure
(hash-code-combine hc ...) → fixnum?
  hc : exact-integer?

hc들을 입력 순서에 의존하는 해시 코드로 결합합니다. 구조체에서 서로 다른 필드들의 해시 코드를 결합하는 데 유용합니다.

예시:

> (require racket/hash-code)

> (struct ordered-triple (fst snd thd)
    #:methods gen:equal+hash
    [(define (equal-proc self other rec)
       (and (rec (ordered-triple-fst self) (ordered-triple-fst other))
            (rec (ordered-triple-snd self) (ordered-triple-snd other))
            (rec (ordered-triple-thd self) (ordered-triple-thd other))))
     (define (hash-proc self rec)
       (hash-code-combine (eq-hash-code struct:ordered-triple)
                          (rec (ordered-triple-fst self))
                          (rec (ordered-triple-snd self))
                          (rec (ordered-triple-thd self))))
     (define (hash2-proc self rec)
       (hash-code-combine (eq-hash-code struct:ordered-triple)
                          (rec (ordered-triple-fst self))
                          (rec (ordered-triple-snd self))
                          (rec (ordered-triple-thd self))))])
> (equal? (ordered-triple 'A 'B 'C) (ordered-triple 'A 'B 'C))
#t
> (= (equal-hash-code (ordered-triple 'A 'B 'C))
     (equal-hash-code (ordered-triple 'A 'B 'C)))
#t
> (equal? (ordered-triple 'A 'B 'C) (ordered-triple 'C 'B 'A))
#f
> (= (equal-hash-code (ordered-triple 'A 'B 'C))
     (equal-hash-code (ordered-triple 'C 'B 'A)))
#f
> (equal? (ordered-triple 'A 'B 'C) (ordered-triple 'C 'A 'B))
#f
> (= (equal-hash-code (ordered-triple 'A 'B 'C))
     (equal-hash-code (ordered-triple 'C 'A 'B)))
#f

한 인자로, (hash-code-combine hc)는 해시 코드가 단지 hc가 아니도록 혼합합니다.

예시:

> (require racket/hash-code)

> (struct wrap (value)
    #:methods gen:equal+hash
    [(define (equal-proc self other rec)
       (rec (wrap-value self) (wrap-value other)))
     (define (hash-proc self rec)
       ; demonstrates `hash-code-combine` with only one argument
       ; but it's good to combine `(eq-hash-code struct:wrap)` too
       (hash-code-combine (rec (wrap-value self))))
     (define (hash2-proc self rec)
       (hash-code-combine (rec (wrap-value self))))])
> (equal? (wrap 'A) (wrap 'A))
#t
> (= (equal-hash-code (wrap 'A))
     (equal-hash-code (wrap 'A)))
#t
> (equal? (wrap 'A) 'A)
#f
> (= (equal-hash-code (wrap 'A))
     (equal-hash-code 'A))
#f
procedure
(hash-code-combine-unordered hc ...) → fixnum?
  hc : exact-integer?

hc들을 입력 순서에 의존하지 않는 해시 코드로 결합합니다. 정렬되지 않은 집합의 요소들의 해시 코드를 결합하는 데 유용합니다.

예시:

> (require racket/hash-code)

> (struct flip-triple (left mid right)
    #:methods gen:equal+hash
    [(define (equal-proc self other rec)
       (and (rec (flip-triple-mid self) (flip-triple-mid other))
            (or
             (and (rec (flip-triple-left self) (flip-triple-left other))
                  (rec (flip-triple-right self) (flip-triple-right other)))
             (and (rec (flip-triple-left self) (flip-triple-right other))
                  (rec (flip-triple-right self) (flip-triple-left other))))))
     (define (hash-proc self rec)
       (hash-code-combine (eq-hash-code struct:flip-triple)
                          (rec (flip-triple-mid self))
                          (hash-code-combine-unordered
                           (rec (flip-triple-left self))
                           (rec (flip-triple-right self)))))
     (define (hash2-proc self rec)
       (hash-code-combine (eq-hash-code struct:flip-triple)
                          (rec (flip-triple-mid self))
                          (hash-code-combine-unordered
                           (rec (flip-triple-left self))
                           (rec (flip-triple-right self)))))])
> (equal? (flip-triple 'A 'B 'C) (flip-triple 'A 'B 'C))
#t
> (= (equal-hash-code (flip-triple 'A 'B 'C))
     (equal-hash-code (flip-triple 'A 'B 'C)))
#t
> (equal? (flip-triple 'A 'B 'C) (flip-triple 'C 'B 'A))
#t
> (= (equal-hash-code (flip-triple 'A 'B 'C))
     (equal-hash-code (flip-triple 'C 'B 'A)))
#t
> (equal? (flip-triple 'A 'B 'C) (flip-triple 'C 'A 'B))
#f
> (= (equal-hash-code (flip-triple 'A 'B 'C))
     (equal-hash-code (flip-triple 'C 'A 'B)))
#f

> (struct rotate-triple (rock paper scissors)
    #:methods gen:equal+hash
    [(define (equal-proc self other rec)
       (or
        (and (rec (rotate-triple-rock self) (rotate-triple-rock other))
             (rec (rotate-triple-paper self) (rotate-triple-paper other))
             (rec (rotate-triple-scissors self) (rotate-triple-scissors other)))
        (and (rec (rotate-triple-rock self) (rotate-triple-paper other))
             (rec (rotate-triple-paper self) (rotate-triple-scissors other))
             (rec (rotate-triple-scissors self) (rotate-triple-rock other)))
        (and (rec (rotate-triple-rock self) (rotate-triple-scissors other))
             (rec (rotate-triple-paper self) (rotate-triple-rock other))
             (rec (rotate-triple-scissors self) (rotate-triple-paper other)))))
     (define (hash-proc self rec)
       (define r (rec (rotate-triple-rock self)))
       (define p (rec (rotate-triple-paper self)))
       (define s (rec (rotate-triple-scissors self)))
       (hash-code-combine
        (eq-hash-code struct:rotate-triple)
        (hash-code-combine-unordered
         (hash-code-combine r p)
         (hash-code-combine p s)
         (hash-code-combine s r))))
     (define (hash2-proc self rec)
       (define r (rec (rotate-triple-rock self)))
       (define p (rec (rotate-triple-paper self)))
       (define s (rec (rotate-triple-scissors self)))
       (hash-code-combine
        (eq-hash-code struct:rotate-triple)
        (hash-code-combine-unordered
         (hash-code-combine r p)
         (hash-code-combine p s)
         (hash-code-combine s r))))])
> (equal? (rotate-triple 'A 'B 'C) (rotate-triple 'A 'B 'C))
#t
> (= (equal-hash-code (rotate-triple 'A 'B 'C))
     (equal-hash-code (rotate-triple 'A 'B 'C)))
#t
> (equal? (rotate-triple 'A 'B 'C) (rotate-triple 'C 'B 'A))
#f
> (= (equal-hash-code (rotate-triple 'A 'B 'C))
     (equal-hash-code (rotate-triple 'C 'B 'A)))
#f
> (equal? (rotate-triple 'A 'B 'C) (rotate-triple 'C 'A 'B))
#t
> (= (equal-hash-code (rotate-triple 'A 'B 'C))
     (equal-hash-code (rotate-triple 'C 'A 'B)))
#t
procedure
(hash-code-combine* hc ... hcs) → fixnum?
  hc : exact-integer?
  hcs : (listof exact-integer?)

hash-code-combine과 같지만, 마지막 인자가 hash-code-combine의 인자 리스트로 사용됩니다. 그래서 (hash-code-combine* hc ... hcs)(apply hash-code-combine hc ... hcs)와 같습니다. 다시 말해, hash-code-combinehash-code-combine*의 관계는 listlist*의 관계와 비슷합니다.

procedure
(hash-code-combine-unordered* hc ... hcs) → fixnum?
  hc : exact-integer?
  hcs : (listof exact-integer?)

hash-code-combine-unordered과 같지만, 마지막 인자가 hash-code-combine-unordered의 인자 리스트로 사용됩니다. 그래서 (hash-code-combine-unordered* hc ... hcs)(apply hash-code-combine-unordered hc ... hcs)와 같습니다. 다시 말해, hash-code-combine-unorderedhash-code-combine-unordered*의 관계는 listlist*의 관계와 비슷합니다.

더 알아보기