Skip to content

Commit 721ba7a

Browse files
authored
Merge pull request #18304 from JuliaLang/nl/nullableops
Fast versions of isequal() and isless() for Nullables
2 parents d7a703f + 4777f7a commit 721ba7a

File tree

4 files changed

+159
-41
lines changed

4 files changed

+159
-41
lines changed

base/nullable.jl

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,88 @@ get(x::Nullable) = x.isnull ? throw(NullException()) : x.value
6363

6464
isnull(x::Nullable) = x.isnull
6565

66-
function isequal(x::Nullable, y::Nullable)
67-
if x.isnull && y.isnull
68-
return true
69-
elseif x.isnull || y.isnull
70-
return false
66+
67+
## Operators
68+
69+
"""
70+
null_safe_op(f::Any, ::Type, ::Type...)::Bool
71+
72+
Returns whether an operation `f` can safely be applied to any value of the passed type(s).
73+
Returns `false` by default.
74+
75+
Custom types should implement methods for some or all operations `f` when applicable:
76+
returning `true` means that the operation may be called on any bit pattern without
77+
throwing an error (though returning invalid or nonsensical results is not a problem).
78+
In particular, this means that the operation can be applied on the whole domain of the
79+
type *and on uninitialized objects*. As a general rule, these properties are only true for
80+
safe operations on `isbits` types.
81+
82+
Types declared as safe can benefit from higher performance for operations on nullable: by
83+
always computing the result even for null values, a branch is avoided, which helps
84+
vectorization.
85+
"""
86+
null_safe_op(f::Any, ::Type, ::Type...) = false
87+
88+
typealias NullSafeSignedInts Union{Int128, Int16, Int32, Int64, Int8}
89+
typealias NullSafeUnsignedInts Union{Bool, UInt128, UInt16, UInt32, UInt64, UInt8}
90+
typealias NullSafeInts Union{NullSafeSignedInts, NullSafeUnsignedInts}
91+
typealias NullSafeFloats Union{Float16, Float32, Float64}
92+
typealias NullSafeTypes Union{NullSafeInts, NullSafeFloats}
93+
94+
null_safe_op{S<:NullSafeTypes,
95+
T<:NullSafeTypes}(::typeof(isequal), ::Type{S}, ::Type{T}) = true
96+
null_safe_op{S<:NullSafeTypes,
97+
T<:NullSafeTypes}(::typeof(isequal), ::Type{Complex{S}}, ::Type{Complex{T}}) = true
98+
null_safe_op{S<:NullSafeTypes,
99+
T<:NullSafeTypes}(::typeof(isequal), ::Type{Rational{S}}, ::Type{Rational{T}}) = true
100+
101+
"""
102+
isequal(x::Nullable, y::Nullable)
103+
104+
If neither `x` nor `y` is null, compare them according to their values
105+
(i.e. `isequal(get(x), get(y))`). Else, return `true` if both arguments are null,
106+
and `false` if one is null but not the other: nulls are considered equal.
107+
"""
108+
@inline function isequal{S,T}(x::Nullable{S}, y::Nullable{T})
109+
if null_safe_op(isequal, S, T)
110+
(x.isnull & y.isnull) | (!x.isnull & !y.isnull & isequal(x.value, y.value))
71111
else
72-
return isequal(x.value, y.value)
112+
(x.isnull & y.isnull) || (!x.isnull & !y.isnull && isequal(x.value, y.value))
73113
end
74114
end
75115

116+
isequal(x::Nullable{Union{}}, y::Nullable{Union{}}) = true
117+
isequal(x::Nullable{Union{}}, y::Nullable) = y.isnull
118+
isequal(x::Nullable, y::Nullable{Union{}}) = x.isnull
119+
120+
null_safe_op{S<:NullSafeTypes,
121+
T<:NullSafeTypes}(::typeof(isless), ::Type{S}, ::Type{T}) = true
122+
null_safe_op{S<:NullSafeTypes,
123+
T<:NullSafeTypes}(::typeof(isless), ::Type{Complex{S}}, ::Type{Complex{T}}) = true
124+
null_safe_op{S<:NullSafeTypes,
125+
T<:NullSafeTypes}(::typeof(isless), ::Type{Rational{S}}, ::Type{Rational{T}}) = true
126+
127+
"""
128+
isless(x::Nullable, y::Nullable)
129+
130+
If neither `x` nor `y` is null, compare them according to their values
131+
(i.e. `isless(get(x), get(y))`). Else, return `true` if only `y` is null, and `false`
132+
otherwise: nulls are always considered greater than non-nulls, but not greater than
133+
another null.
134+
"""
135+
@inline function isless{S,T}(x::Nullable{S}, y::Nullable{T})
136+
# NULL values are sorted last
137+
if null_safe_op(isless, S, T)
138+
(!x.isnull & y.isnull) | (!x.isnull & !y.isnull & isless(x.value, y.value))
139+
else
140+
(!x.isnull & y.isnull) || (!x.isnull & !y.isnull && isless(x.value, y.value))
141+
end
142+
end
143+
144+
isless(x::Nullable{Union{}}, y::Nullable{Union{}}) = false
145+
isless(x::Nullable{Union{}}, y::Nullable) = false
146+
isless(x::Nullable, y::Nullable{Union{}}) = !x.isnull
147+
76148
==(x::Nullable, y::Nullable) = throw(NullException())
77149

78150
const nullablehash_seed = UInt === UInt64 ? 0x932e0143e51d0171 : 0xe51d0171

doc/stdlib/base.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,12 +263,24 @@ All Objects
263263

264264
Scalar types generally do not need to implement ``isequal`` separate from ``==``\ , unless they represent floating-point numbers amenable to a more efficient implementation than that provided as a generic fallback (based on ``isnan``\ , ``signbit``\ , and ``==``\ ).
265265

266+
.. function:: isequal(x::Nullable, y::Nullable)
267+
268+
.. Docstring generated from Julia source
269+
270+
If neither ``x`` nor ``y`` is null, compare them according to their values (i.e. ``isequal(get(x), get(y))``\ ). Else, return ``true`` if both arguments are null, and ``false`` if one is null but not the other: nulls are considered equal.
271+
266272
.. function:: isless(x, y)
267273

268274
.. Docstring generated from Julia source
269275
270276
Test whether ``x`` is less than ``y``\ , according to a canonical total order. Values that are normally unordered, such as ``NaN``\ , are ordered in an arbitrary but consistent fashion. This is the default comparison used by ``sort``\ . Non-numeric types with a canonical total order should implement this function. Numeric types only need to implement it if they have special values such as ``NaN``\ .
271277

278+
.. function:: isless(x::Nullable, y::Nullable)
279+
280+
.. Docstring generated from Julia source
281+
282+
If neither ``x`` nor ``y`` is null, compare them according to their values (i.e. ``isless(get(x), get(y))``\ ). Else, return ``true`` if only ``y`` is null, and ``false`` otherwise: nulls are always considered greater than non-nulls, but not greater than another null.
283+
272284
.. function:: ifelse(condition::Bool, x, y)
273285

274286
.. Docstring generated from Julia source

doc/stdlib/stacktraces.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* ``func::Symbol``
1818

1919
The name of the function containing the execution context.
20-
* ``linfo::Nullable{MethodInstance}``
20+
* ``linfo::Nullable{Core.MethodInstance}``
2121

2222
The MethodInstance containing the execution context (if it could be found).
2323
* ``file::Symbol``

test/nullable.jl

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -174,40 +174,6 @@ end
174174

175175
@test isnull(Nullable())
176176

177-
# function isequal{S, T}(x::Nullable{S}, y::Nullable{T})
178-
for T in types
179-
x0 = Nullable()
180-
x1 = Nullable{T}()
181-
x2 = Nullable{T}()
182-
x3 = Nullable(zero(T))
183-
x4 = Nullable(one(T))
184-
185-
@test isequal(x0, x1) === true
186-
@test isequal(x0, x2) === true
187-
@test isequal(x0, x3) === false
188-
@test isequal(x0, x4) === false
189-
190-
@test isequal(x1, x1) === true
191-
@test isequal(x1, x2) === true
192-
@test isequal(x1, x3) === false
193-
@test isequal(x1, x4) === false
194-
195-
@test isequal(x2, x1) === true
196-
@test isequal(x2, x2) === true
197-
@test isequal(x2, x3) === false
198-
@test isequal(x2, x4) === false
199-
200-
@test isequal(x3, x1) === false
201-
@test isequal(x3, x2) === false
202-
@test isequal(x3, x3) === true
203-
@test isequal(x3, x4) === false
204-
205-
@test isequal(x4, x1) === false
206-
@test isequal(x4, x2) === false
207-
@test isequal(x4, x3) === false
208-
@test isequal(x4, x4) === true
209-
end
210-
211177
# function =={S, T}(x::Nullable{S}, y::Nullable{T})
212178
for T in types
213179
x0 = Nullable()
@@ -279,6 +245,74 @@ for T in types
279245
@test get(x1.v, one(T)) === one(T)
280246
end
281247

248+
# Operators
249+
TestTypes = Union{Base.NullSafeTypes, BigInt, BigFloat,
250+
Complex{Int}, Complex{Float64}, Complex{BigFloat},
251+
Rational{Int}, Rational{BigInt}}.types
252+
for S in TestTypes, T in TestTypes
253+
u0 = zero(S)
254+
u1 = one(S)
255+
if S <: AbstractFloat
256+
u2 = S(NaN)
257+
elseif S <: Complex && S.parameters[1] <: AbstractFloat
258+
u2 = S(NaN, NaN)
259+
else
260+
u2 = u1
261+
end
262+
263+
v0 = zero(T)
264+
v1 = one(T)
265+
if T <: AbstractFloat
266+
v2 = T(NaN)
267+
elseif T <: Complex && T.parameters[1] <: AbstractFloat
268+
v2 = T(NaN, NaN)
269+
else
270+
v2 = v1
271+
end
272+
273+
for u in (u0, u1, u2), v in (v0, v1, v2)
274+
# function isequal(x::Nullable, y::Nullable)
275+
@test isequal(Nullable(u), Nullable(v)) === isequal(u, v)
276+
@test isequal(Nullable(u), Nullable(u)) === true
277+
@test isequal(Nullable(v), Nullable(v)) === true
278+
279+
@test isequal(Nullable(u), Nullable(v, true)) === false
280+
@test isequal(Nullable(u, true), Nullable(v)) === false
281+
@test isequal(Nullable(u, true), Nullable(v, true)) === true
282+
283+
@test isequal(Nullable(u), Nullable{T}()) === false
284+
@test isequal(Nullable{S}(), Nullable(v)) === false
285+
@test isequal(Nullable{S}(), Nullable{T}()) === true
286+
287+
@test isequal(Nullable(u), Nullable()) === false
288+
@test isequal(Nullable(), Nullable(v)) === false
289+
@test isequal(Nullable{S}(), Nullable()) === true
290+
@test isequal(Nullable(), Nullable{T}()) === true
291+
@test isequal(Nullable(), Nullable()) === true
292+
293+
# function isless(x::Nullable, y::Nullable)
294+
if S <: Real && T <: Real
295+
@test isless(Nullable(u), Nullable(v)) === isless(u, v)
296+
@test isless(Nullable(u), Nullable(u)) === false
297+
@test isless(Nullable(v), Nullable(v)) === false
298+
299+
@test isless(Nullable(u), Nullable(v, true)) === true
300+
@test isless(Nullable(u, true), Nullable(v)) === false
301+
@test isless(Nullable(u, true), Nullable(v, true)) === false
302+
303+
@test isless(Nullable(u), Nullable{T}()) === true
304+
@test isless(Nullable{S}(), Nullable(v)) === false
305+
@test isless(Nullable{S}(), Nullable{T}()) === false
306+
307+
@test isless(Nullable(u), Nullable()) === true
308+
@test isless(Nullable(), Nullable(v)) === false
309+
@test isless(Nullable{S}(), Nullable()) === false
310+
@test isless(Nullable(), Nullable{T}()) === false
311+
@test isless(Nullable(), Nullable()) === false
312+
end
313+
end
314+
end
315+
282316
# issue #9462
283317
for T in types
284318
@test isa(convert(Nullable{Number}, Nullable(one(T))), Nullable{Number})

0 commit comments

Comments
 (0)