-
Notifications
You must be signed in to change notification settings - Fork 1
/
comparison.go
279 lines (238 loc) · 8.41 KB
/
comparison.go
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
package gg
import (
r "reflect"
u "unsafe"
)
/*
Returns true if the input is the zero value of its type. Optionally falls back
on `Zeroable.IsZero` if the input implements this interface. Support for
`Zeroable` is useful for types such as `time.Time`, where "zero" is determined
only by the timestamp, ignoring the timezone.
*/
func IsZero[A any](val A) bool {
box := AnyNoEscUnsafe(val)
if box == nil {
return true
}
/**
When possible, we want to "natively" compare to a zero value before invoking
`Zeroable`. Depending on the Go version, this may slightly speed us up, or
slightly slow us down. More importantly, this improves correctness,
safeguarding us against bizarre implementations of `Zeroable` such as
`reflect.Value.IsZero`, which panics when called on the zero value of
`reflect.Value`.
It would be ideal to compare concrete values rather than `any`, but requiring
`comparable` would make this function much less usable, and comparing `any`
seems fast enough.
*/
if Type[A]().Comparable() {
if box == AnyNoEscUnsafe(Zero[A]()) {
return true
}
/**
Terminate here to avoid falling down to the reflection-based clause.
We already know that our value is not considered zero by Go.
*/
impl, _ := box.(Zeroable)
return impl != nil && impl.IsZero()
}
impl, _ := box.(Zeroable)
if impl != nil {
return impl.IsZero()
}
/**
Implementation note. For some types, some non-zero byte patterns are
considered a zero value. The most notable example is strings. For example,
the following expression makes a string with a non-zero data pointer, which
is nevertheless considered a zero value by Go:
`some_text`[:0]
`reflect.Value.IsZero` correctly handles such cases, and doesn't seem to be
outrageously slow.
*/
return r.ValueOf(box).IsZero()
}
// Inverse of `IsZero`.
func IsNotZero[A any](val A) bool { return !IsZero(val) }
/*
True if every byte in the given value is zero. Not equivalent to `IsZero`.
Most of the time, you should prefer `IsZero`, which is more performant and
more correct.
*/
func IsTrueZero[A any](val A) bool {
size := u.Sizeof(val)
for off := uintptr(0); off < size; off++ {
if *(*byte)(u.Pointer(uintptr(u.Pointer(&val)) + off)) != 0 {
return false
}
}
return true
}
// Generic variant of `Nullable.IsNull`.
func IsNull[A Nullable](val A) bool { return val.IsNull() }
// Inverse of `IsNull`.
func IsNotNull[A Nullable](val A) bool { return !val.IsNull() }
/*
True if the inputs are byte-for-byte identical. This function is not meant for
common use. Nearly always, you should use `Eq` or `Equal` instead. This one is
sometimes useful for testing purposes, such as asserting that two interface
values refer to the same underlying data. This may lead to brittle code that is
not portable between different Go implementations. Performance is similar to
`==` for small value sizes (up to 2-4 machine words) but is significantly worse
for large value sizes.
*/
func Is[A any](one, two A) bool {
/**
Note. The "ideal" implementation looks like this:
const size = u.Sizeof(one)
return CastUnsafe[[size]byte](one) == CastUnsafe[[size]byte](two)
But at the time of writing, in Go 1.19, `unsafe.Sizeof` on a type parameter
is considered non-constant. If this changes in the future, we'll switch to
the implementation above.
*/
size := u.Sizeof(one)
switch size {
case 0:
return true
case SizeofWord:
return *(*uint)(u.Pointer(&one)) == *(*uint)(u.Pointer(&two))
// Common case: comparing interfaces or strings.
case SizeofWord * 2:
return (*(*uint)(u.Pointer(&one)) == *(*uint)(u.Pointer(&two))) &&
(*(*uint)(u.Pointer(uintptr(u.Pointer(&one)) + SizeofWord)) ==
*(*uint)(u.Pointer(uintptr(u.Pointer(&two)) + SizeofWord)))
// Common case: comparing slices.
case SizeofWord * 3:
return (*(*uint)(u.Pointer(&one)) == *(*uint)(u.Pointer(&two))) &&
(*(*uint)(u.Pointer(uintptr(u.Pointer(&one)) + SizeofWord)) ==
*(*uint)(u.Pointer(uintptr(u.Pointer(&two)) + SizeofWord))) &&
(*(*uint)(u.Pointer(uintptr(u.Pointer(&one)) + SizeofWord*2)) ==
*(*uint)(u.Pointer(uintptr(u.Pointer(&two)) + SizeofWord*2)))
default:
/**
Implementation note. We could also walk word-by-word by using padded structs
to ensure sufficient empty memory. It would improve the performance
slightly, but not enough to bother. The resulting performance is still
much worse than `==` on whole values.
*/
for off := uintptr(0); off < size; off++ {
oneChunk := *(*byte)(u.Pointer(uintptr(u.Pointer(&one)) + off))
twoChunk := *(*byte)(u.Pointer(uintptr(u.Pointer(&two)) + off))
if oneChunk != twoChunk {
return false
}
}
return true
}
}
// Same as `==`. Sometimes useful with higher-order functions.
func Eq[A comparable](one, two A) bool { return one == two }
/*
Short for "equal". For types that implement `Equaler`, this simply calls their
equality method. Otherwise falls back on `reflect.DeepEqual`. Compared to
`reflect.DeepEqual`, this has better type safety, and in many cases this has
better performance, even when calling `reflect.DeepEqual` in fallback mode.
*/
func Equal[A any](one, two A) bool {
impl, _ := AnyNoEscUnsafe(one).(Equaler[A])
if impl != nil {
return impl.Equal(two)
}
return r.DeepEqual(AnyNoEscUnsafe(one), AnyNoEscUnsafe(two))
}
/*
True if the inputs are equal via `==`, and neither is a zero value of its type.
For non-equality, use `NotEqNotZero`.
*/
func EqNotZero[A comparable](one, two A) bool {
return one == two && one != Zero[A]()
}
/*
True if the inputs are non-equal via `!=`, and at least one is not a zero value
of its type. For equality, use `EqNotZero`.
*/
func NotEqNotZero[A comparable](one, two A) bool {
return one != two && one != Zero[A]()
}
/*
True if the given slice headers are byte-for-byte identical. In other words,
true if the given slices have the same data pointer, length, capacity. Does not
compare individual elements.
*/
func SliceIs[A any](one, two []A) bool {
return CastUnsafe[r.SliceHeader](one) == CastUnsafe[r.SliceHeader](two)
}
// Returns the first non-zero value from among the inputs.
func Or[A any](val ...A) A { return Find(val, IsNotZero[A]) }
/*
Variant of `Or` compatible with `Nullable`. Returns the first non-"null" value
from among the inputs.
*/
func NullOr[A Nullable](val ...A) A { return Find(val, IsNotNull[A]) }
// Version of `<` for non-primitives that implement `Lesser`.
func Less2[A Lesser[A]](one, two A) bool { return one.Less(two) }
/*
Variadic version of `<` for non-primitives that implement `Lesser`.
Shortcut for `IsSorted` with `Less2`.
*/
func Less[A Lesser[A]](src ...A) bool { return IsSorted(src, Less2[A]) }
// Same as Go's `<` operator, expressed as a generic function.
func LessPrim2[A LesserPrim](one, two A) bool { return one < two }
/*
Variadic version of `<` for non-primitives that implement `Lesser`.
Shortcut for `IsSorted` with `LessPrim2`.
*/
func LessPrim[A LesserPrim](src ...A) bool { return IsSorted(src, LessPrim2[A]) }
// Version of `<=` for non-primitives that implement `Lesser`.
func LessEq2[A Lesser[A]](one, two A) bool { return one.Less(two) || Equal(one, two) }
/*
Variadic version of `<=` for non-primitives that implement `Lesser`.
Shortcut for `IsSorted` with `LessEq2`.
*/
func LessEq[A Lesser[A]](src ...A) bool { return IsSorted(src, LessEq2[A]) }
// Same as Go's `<=` operator, expressed as a generic function.
func LessEqPrim2[A LesserPrim](one, two A) bool { return one <= two }
/*
Variadic version of Go's `<=` operator.
Shortcut for `IsSorted` with `LessEqPrim2`.
*/
func LessEqPrim[A LesserPrim](src ...A) bool { return IsSorted(src, LessEqPrim2[A]) }
/*
Returns the lesser of the two inputs. For primitive types that don't implement
`Lesser`, see `MinPrim2`. For a variadic variant, see `Min`.
*/
func Min2[A Lesser[A]](one, two A) A {
if one.Less(two) {
return one
}
return two
}
/*
Returns the lesser of the two inputs, which must be comparable primitives. For
non-primitives, see `Min2`. For a variadic variant, see `MinPrim`.
*/
func MinPrim2[A LesserPrim](one, two A) A {
if one < two {
return one
}
return two
}
/*
Returns the larger of the two inputs. For primitive types that don't implement
`Lesser`, see `MaxPrim2`. For a variadic variant, see `Max`.
*/
func Max2[A Lesser[A]](one, two A) A {
if one.Less(two) {
return two
}
return one
}
/*
Returns the larger of the two inputs, which must be comparable primitives. For
non-primitives, see `Max2`. For a variadic variant, see `MaxPrim`.
*/
func MaxPrim2[A LesserPrim](one, two A) A {
if one < two {
return two
}
return one
}