-
Notifications
You must be signed in to change notification settings - Fork 0
/
equiv.lisp
115 lines (105 loc) · 4.14 KB
/
equiv.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
(in-package #:EQUIV)
(defgeneric object-constituents (type)
(:documentation "Returns list of accessors used to determine
equivalence of objects of type TYPE.")
(:method ((type (eql 'cons)))
(load-time-value (list #'car #'cdr)))
(:method ((type (eql 'pathname)))
(load-time-value
(list #'pathname-directory
#'pathname-name
#'pathname-type
#'pathname-version
#'pathname-host
#'pathname-device))))
(defgeneric object-frozenp (object)
(:documentation "Indicates whether OBJECT is frozen. That is,
this function may return true only if OBJECT will not be mutated
in an observable way from the point of the call until the end of
its life time, otherwise false.")
(:method (object)
(declare (ignore object))
nil)
(:method ((cons cons))
(declare (ignore cons))
nil)
(:method ((string string))
(declare (ignore string))
nil)
(:method ((vector vector))
(declare (ignore vector))
nil)
(:method ((pathname pathname))
(declare (ignore pathname))
t)
(:method ((number number))
(declare (ignore number))
t)
(:method ((char character))
(declare (ignore char))
t))
(declaim (inline object-sequence= object-vector=))
(defun object-sequence= (xs ys)
"Checks whether sequences XS and YS are element-wise equivalent,
by means of OBJECT=, and of the same length."
(and (= (length xs) (length ys))
(every #'object= xs ys)))
(defun object-vector= (xs ys)
"Checks whether vectors XS and YS are element-wise equivalent,
by means of OBJECT=, and of the same length.
Use OBJECT-SEQUENCE= instead."
(object-sequence= xs ys))
(defun object= (x y &optional frozenp)
"Returns true if X and Y are (observationally) equivalent.
Hence, OBJECT= is an equivalence relation:
1. (object= x x)
2. (equal (object= x y frozenp) (object= y x frozenp))
3. (implies (and (object= x y frozenp) (object= y z frozenp))
(object= x z frozenp))
Frozen objects \(i.e., objects which are promised not to mutate)
are compared by recursing into their constituents, as specified by
OBJECT-CONSTITUENTS. Mutable \(i.e., not frozen) objects are
compared with the pointer equality EQ.
FROZENP can be used to override the defaults for X and Y given by
OBJECT-FROZENP. It is a promise that none of the objects X and Y
are referring to with their constituents, or **any of the
constituents' constituents** are mutated from the time of the
call to OBJECT= onwards.
If one lies with FROZENP, OBJECT-FROZENP, or OBJECT-CONSTITUENTS,
all bets are off and the result of OBJECT= is meaningless.
OBJECT= diverges if both X and Y are circular data structures.
See also: <http://home.pipeline.com/~hbaker1/ObjectIdentity.html>"
(declare (optimize (speed 3)))
(labels
((object=/rec (x y)
(cond ((eq x y) t)
;; special cases
((stringp x)
(and (stringp y) frozenp (string= x y)))
((vectorp x)
(and (vectorp y) frozenp (object-vector= x y)))
((bit-vector-p x)
(and frozenp (equal x y)))
((not (equal (type-of x) (type-of y))) nil)
;; from here: objects which have the same type
;; from here: immutable objects
((numberp x) (= x y))
((characterp x) (char= x y))
((pathnamep x) (equal x y))
((not (or frozenp
(and (object-frozenp x)
(object-frozenp y)))) nil)
;; from here: only frozen mutable objects
((consp x)
(loop
(cond ((not (object=/rec (pop x) (pop y)))
(return nil))
((not (and (consp x) (consp y)))
(return (object=/rec x y))))))
(t (let ((constituents (object-constituents (type-of x))))
(and constituents
(every (lambda (key)
(object=/rec (funcall key x)
(funcall key y)))
constituents)))))))
(object=/rec x y)))