R6RS Scheme: 동등 술어
R6RS Scheme: 동등 술어
객체 간의 동등 관계를 검사하는 eqv?, eq?, equal? 술어를 설명해요. 각 술어가 무엇을 비교하고 서로 어떻게 다른지, 그리고 각각 언제 #t·#f·미지정 값을 반환하는지 규칙과 예시로 다뤄요.
출처: 문서
본문
술어(predicate)는 항상 불리언 값(#t 또는 #f)을 반환하는 프로시저예요. 동등 술어(equivalence predicate)는 수학적 동등 관계의 계산적 유사체예요(그것은 대칭적이고, 반사적이며, 이행적이에요). 이 절에서 설명하는 동등 술어들 중 eq?는 가장 정밀한, 즉 가장 잘 식별하는 것이고, equal?는 가장 거친 것이에요. eqv? 술어는 eq?보다 약간 덜 식별적이에요.
(eqv? obj1 obj2) — procedure
eqv? 프로시저는 객체들에 대한 유용한 동등 관계를 정의해요. 간단히 말하면, obj1과 obj2가 보통 같은 객체로 간주되어야 하면 #t를, 그렇지 않으면 #f를 반환해요. 이 관계는 해석의 여지가 약간 남아 있지만, eqv?에 대한 다음의 부분 명세는 모든 구현에서 성립해야 해요.
eqv? 프로시저는 다음 중 하나가 성립하면 #t를 반환해요:
obj1과obj2가 둘 다 불리언이고boolean=?프로시저(11.8절)에 따라 같아요.obj1과obj2가 둘 다 기호이고symbol=?프로시저(11.10절)에 따라 같아요.obj1과obj2가 둘 다 정확한 수치 객체이고 수치적으로 같아요(=, 11.7절 참조).obj1과obj2가 둘 다 부정확한 수치 객체이고, 수치적으로 같으면서(=, 11.7절 참조), Scheme의 표준 산술 프로시저들의 유한 합성으로 정의될 수 있는 다른 어떤 프로시저에 인자로 전달될 때eqv?의 의미에서 같은 결과를 산출해요.obj1과obj2가 둘 다 문자이고char=?프로시저(11.11절)에 따라 같은 문자예요.obj1과obj2가 둘 다 빈 리스트예요.obj1과obj2가 저장소(store, 5.10절)의 같은 위치를 가리키는 객체들인데, 예를 들어 쌍, 벡터, 바이트벡터("Bytevectors" 라이브러리 장), 문자열, 해시테이블("Hash tables" 라이브러리 장), 레코드("Records" 라이브러리 장), 포트("Port I/O" 라이브러리 절) 따위예요.obj1과obj2가 "Procedural layer" 라이브러리 절에서eqv?인 것으로 명시된 레코드 타입 기술자(record-type descriptor)예요.
eqv? 프로시저는 다음 중 하나가 성립하면 #f를 반환해요:
obj1과obj2가 다른 타입(11.1절)이에요.obj1과obj2가boolean=?프로시저가#f를 반환하는 불리언들이에요.obj1과obj2가symbol=?프로시저가#f를 반환하는 기호들이에요.obj1과obj2중 하나는 정확한 수치 객체인데 다른 하나는 부정확한 수치 객체예요.obj1과obj2가=프로시저가#f를 반환하는 유리수 수치 객체들이에요.obj1과obj2가 Scheme의 표준 산술 프로시저들의 유한 합성으로 정의될 수 있는 다른 어떤 프로시저에 인자로 전달될 때eqv?의 의미에서 다른 결과를 산출해요.obj1과obj2가char=?프로시저가#f를 반환하는 문자들이에요.obj1과obj2중 하나는 빈 리스트인데 다른 하나는 아니에요.obj1과obj2가 서로 다른 위치를 가리키는 객체들인데, 예를 들어 쌍, 벡터, 바이트벡터("Bytevectors" 라이브러리 장), 문자열, 레코드("Records" 라이브러리 장), 포트("Port I/O" 라이브러리 절), 해시테이블("Hashtables" 라이브러리 장) 따위예요.obj1과obj2가 쌍, 벡터, 문자열, 레코드 또는 해시테이블인데, 둘 다에 같은 접근자(즉car,cdr,vector-ref,string-ref, 또는 레코드 접근자)를 적용했을 때eqv?가#f를 반환하는 결과를 산출해요.obj1과obj2가 어떤 인자들에 대해 다르게 동작할(다른 값을 반환하거나 다른 부수 효과를 가질) 프로시저들이에요.
Note:
obj1과obj2가 수치 객체일 때eqv?프로시저가#t를 반환한다고 해서=도obj1과obj2를 인자로 호출할 때#t를 반환한다는 뜻은 아니에요.
(eqv? 'a 'a) ⇒ #t
(eqv? 'a 'b) ⇒ #f
(eqv? 2 2) ⇒ #t
(eqv? '() '()) ⇒ #t
(eqv? 100000000 100000000) ⇒ #t
(eqv? (cons 1 2) (cons 1 2)) ⇒ #f
(eqv? (lambda () 1)
(lambda () 2)) ⇒ #f
(eqv? #f 'nil) ⇒ #f
다음 예들은 위의 규칙들이 eqv?의 동작을 완전히 명시하지 않는 경우를 보여줘요. 그러한 경우에 대해 말할 수 있는 전부는, eqv?가 반환하는 값이 불리언이어야 한다는 것이에요.
(let ((p (lambda (x) x)))
(eqv? p p)) ⇒ unspecified
(eqv? "" "") ⇒ unspecified
(eqv? '#() '#()) ⇒ unspecified
(eqv? (lambda (x) x)
(lambda (x) x)) ⇒ unspecified
(eqv? (lambda (x) x)
(lambda (y) y)) ⇒ unspecified
(eqv? +nan.0 +nan.0) ⇒ unspecified
다음 예시 세트는 지역 상태를 가진 프로시저들과 함께 eqv?를 사용하는 것을 보여줘요. gen-counter에 대한 호출은 각 프로시저가 자기만의 내부 카운터를 가지므로 매번 서로 다른 프로시저를 반환해야 해요. gen-loser에 대한 호출은 호출될 때 동등하게 동작하는 프로시저들을 반환해요. 그러나 eqv?는 이 동등성을 검출하지 못할 수 있어요.
(define gen-counter
(lambda ()
(let ((n 0))
(lambda () (set! n (+ n 1)) n))))
(let ((g (gen-counter)))
(eqv? g g)) ⇒ unspecified
(eqv? (gen-counter) (gen-counter))
⇒ #f
(define gen-loser
(lambda ()
(let ((n 0))
(lambda () (set! n (+ n 1)) 27))))
(let ((g (gen-loser)))
(eqv? g g)) ⇒ unspecified
(eqv? (gen-loser) (gen-loser))
⇒ unspecified
(letrec ((f (lambda () (if (eqv? f g) 'both 'f)))
(g (lambda () (if (eqv? f g) 'both 'g))))
(eqv? f g)) ⇒ unspecified
(letrec ((f (lambda () (if (eqv? f g) 'f 'both)))
(g (lambda () (if (eqv? f g) 'g 'both))))
(eqv? f g)) ⇒ #f
구현들은 적절한 곳에서 상수들 사이의 구조를 공유할 수 있어요. 게다가, 상수는 11.4.1절에서 언급한 것처럼 서로 다른 위치 집합들에 동시에 존재하도록 구현에 의해 언제든지 복사될 수 있어요. 따라서 상수들에 대한 eqv?의 값은 때때로 구현 의존이에요.
(eqv? '(a) '(a)) ⇒ unspecified
(eqv? "a" "a") ⇒ unspecified
(eqv? '(b) (cdr '(a b))) ⇒ unspecified
(let ((x '(a)))
(eqv? x x)) ⇒ #t
(eq? obj1 obj2) — procedure
eq? 술어는 eqv?와 비슷하되, 어떤 경우에는 eqv?가 검출할 수 있는 것보다 더 미세한 구별들을 식별할 수 있다는 점이 달라요.
eq?와 eqv? 술어는 기호, 불리언, 빈 리스트, 쌍, 프로시저, 비-빈 문자열, 바이트벡터, 벡터, 레코드에 대해 같은 동작을 가지는 것이 보장돼요. 수치 객체와 문자에 대한 eq?의 동작은 구현 의존이지만, 그것은 항상 #t 또는 #f를 반환하고, eqv?도 #t를 반환할 때에만 #t를 반환해요. eq? 술어는 빈 벡터, 빈 바이트벡터, 빈 문자열에서 eqv?와 다르게 동작할 수도 있어요.
(eq? 'a 'a) ⇒ #t
(eq? '(a) '(a)) ⇒ unspecified
(eq? (list 'a) (list 'a)) ⇒ #f
(eq? "a" "a") ⇒ unspecified
(eq? "" "") ⇒ unspecified
(eq? '() '()) ⇒ #t
(eq? 2 2) ⇒ unspecified
(eq? #\A #\A) ⇒ unspecified
(eq? car car) ⇒ #t
(let ((n (+ 2 3)))
(eq? n n)) ⇒ unspecified
(let ((x '(a)))
(eq? x x)) ⇒ #t
(let ((x '#()))
(eq? x x)) ⇒ unspecified
(let ((p (lambda (x) x)))
(eq? p p)) ⇒ unspecified
(equal? obj1 obj2) — procedure
equal? 술어는 (어쩌면 무한한) 인자들의 규칙 트리로의 전개가 정렬된 트리로서 같을 때 그리고 오직 그때 #t를 반환해요.
equal? 술어는 쌍과 벡터를 나가는 변(outgoing edge)을 가진 노드로 취급하고, 문자열을 비교하는 데 string=?를, 바이트벡터를 비교하는 데 bytevector=?를 사용하며("Bytevectors" 라이브러리 장 참조), 다른 노드들을 비교하는 데 eqv?를 사용해요.
(equal? 'a 'a) ⇒ #t
(equal? '(a) '(a)) ⇒ #t
(equal? '(a (b) c)
'(a (b) c)) ⇒ #t
(equal? "abc" "abc") ⇒ #t
(equal? 2 2) ⇒ #t
(equal? (make-vector 5 'a)
(make-vector 5 'a)) ⇒ #t
(equal? '#vu8(1 2 3 4 5)
(u8-list->bytevector
'(1 2 3 4 5)) ⇒ #t
(equal? (lambda (x) x)
(lambda (y) y)) ⇒ unspecified
(let* ((x (list 'a))
(y (list 'a))
(z (list x y)))
(list (equal? z (list y x))
(equal? z (list x x))))
⇒ (#t #t)
Note:
equal?프로시저는 인자들이 사이클을 포함하더라도 항상 종료해야 해요.