This repository has been archived by the owner on May 4, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce a new null_safe_op() function which is used instead of isbits() to decide whether to take the fast path (without a branch) or the slow path in lifted operators. This is more correct (and thus safer), and allows supporting operations on any type. Also add an extensive set of tests for both paths. On Julia 0.4, operators for which no functor exist cannot be identified by null_safe_op(): for these, fall back to the slow path on that version. This PR introduces cbrt as a new supported operation since it has a special operator ∛. It also keeps abs(), abs2(), scalarmin() and scalarmax(), which should probably be deprecated once we have a more general solution for lifting.
- Loading branch information
Showing
2 changed files
with
258 additions
and
147 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,121 +1,128 @@ | ||
import Compat: @functorize | ||
## Lifted operators | ||
|
||
@noinline throw_error() = error() | ||
importall Base.Operators | ||
import Base: promote_op, abs, abs2, sqrt, cbrt, scalarmin, scalarmax | ||
using Compat: @functorize | ||
|
||
for f in ( | ||
:(@compat Base.:+), | ||
:(@compat Base.:-), | ||
:(@compat Base.:!), | ||
:(@compat Base.:~), | ||
) | ||
@eval begin | ||
@inline function $(f){S}(x::Nullable{S}) | ||
if isbits(S) | ||
Nullable($(f)(x.value), x.isnull) | ||
else | ||
throw_error() | ||
end | ||
end | ||
end | ||
# Methods adapted from Base Julia 0.5 | ||
if VERSION <= v"0.5.0-dev" | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
tkelman
Contributor
|
||
promote_op(::Any, T) = T | ||
promote_op{T}(::Type{T}, ::Any) = T | ||
|
||
promote_op{R<:Number}(op, ::Type{R}) = typeof(op(one(R))) | ||
promote_op{R<:Number,S<:Number}(op, ::Type{R}, ::Type{S}) = typeof(op(one(R), one(S))) | ||
end | ||
|
||
# Implement the binary operators: +, -, *, /, %, &, |, ^, <<, and >> | ||
for f in ( | ||
:(@compat Base.:+), | ||
:(@compat Base.:-), | ||
:(@compat Base.:*), | ||
:(@compat Base.:/), | ||
:(@compat Base.:%), | ||
:(@compat Base.:&), | ||
:(@compat Base.:|), | ||
:(@compat Base.:^), | ||
:(@compat Base.:<<), | ||
:(@compat Base.:>>), | ||
) | ||
""" | ||
null_safe_op(f::Any, ::Type)::Bool | ||
null_safe_op(f::Any, ::Type, ::Type)::Bool | ||
Returns whether an operation `f` can safely be applied to any value of the passed type(s). | ||
Returns `false` by default. | ||
Custom types should implement methods for some or all operations `f` when applicable: | ||
returning `true` means that the operation may be called on any bit pattern without | ||
throwing an error (though returning invalid or nonsensical results is not a problem). | ||
In particular, this means that the operation can be applied on the whole domain of the | ||
type *and on uninitialized objects*. As a general rule, these proporties are only true for | ||
safe operations on `isbits` types. | ||
Types declared as safe can benefit from higher performance for operations on nullable: by | ||
always computing the result even for null values, a branch is avoided, which helps | ||
vectorization. | ||
""" | ||
null_safe_op(f::Any, ::Type) = false | ||
null_safe_op(f::Any, ::Type, ::Type) = false | ||
|
||
typealias SafeSignedInts Union{Int128,Int16,Int32,Int64,Int8} | ||
typealias SafeUnsignedInts Union{Bool,UInt128,UInt16,UInt32,UInt64,UInt8} | ||
typealias SafeInts Union{SafeSignedInts,SafeUnsignedInts} | ||
typealias SafeFloats Union{Float16,Float32,Float64} | ||
|
||
# Float types appear in both since they promote to themselves, | ||
# and therefore can't fail due to conversion of negative numbers | ||
typealias SafeSigned Union{SafeSignedInts,SafeFloats} | ||
typealias SafeUnsigned Union{SafeUnsignedInts,SafeFloats} | ||
typealias SafeTypes Union{SafeInts,SafeFloats} | ||
|
||
# Unary operators | ||
|
||
# Note this list does not include sqrt since it can raise an error, | ||
# nor cbrt (for which there is no functor on Julia 0.4) | ||
for op in (:+, :-, :abs, :abs2) | ||
@eval begin | ||
@inline function $(f){S1, S2}(x::Nullable{S1}, y::Nullable{S2}) | ||
if isbits(S1) & isbits(S2) | ||
Nullable($(f)(x.value, y.value), x.isnull | y.isnull) | ||
else | ||
throw_error() | ||
end | ||
end | ||
null_safe_op{T<:SafeTypes}(::typeof(@functorize($op)), ::Type{T}) = true | ||
end | ||
end | ||
|
||
# Implement the binary operators: == and != | ||
for f in ( | ||
:(@compat Base.:(==)), | ||
:(@compat Base.:!=), | ||
) | ||
@eval begin | ||
function $(f){S1, S2}(x::Nullable{S1}, y::Nullable{S2}) | ||
if isbits(S1) & isbits(S2) | ||
Nullable{Bool}($(f)(x.value, y.value), x.isnull | y.isnull) | ||
else | ||
error() | ||
end | ||
end | ||
end | ||
# No functors for these methods on 0.4: use the slow path | ||
if VERSION >= v"0.5.0-dev" | ||
null_safe_op{T<:SafeInts}(::typeof(~), ::Type{T}) = true | ||
null_safe_op{T<:SafeTypes}(::typeof(cbrt), ::Type{T}) = true | ||
null_safe_op(::typeof(!), ::Type{Bool}) = true | ||
end | ||
|
||
# Implement the binary operators: <, >, <=, and >= | ||
for f in ( | ||
:(@compat Base.:<), | ||
:(@compat Base.:>), | ||
:(@compat Base.:<=), | ||
:(@compat Base.:>=), | ||
) | ||
for op in (:+, :-, :!, :~, :abs, :abs2, :sqrt, :cbrt) | ||
@eval begin | ||
function $(f){S1, S2}(x::Nullable{S1}, y::Nullable{S2}) | ||
if isbits(S1) & isbits(S2) | ||
Nullable{Bool}($(f)(x.value, y.value), x.isnull | y.isnull) | ||
@inline function $op{S}(x::Nullable{S}) | ||
R = promote_op($op, S) | ||
if null_safe_op(@functorize($op), S) | ||
Nullable{R}($op(x.value), x.isnull) | ||
else | ||
error() | ||
x.isnull ? Nullable{R}() : | ||
Nullable{R}($op(x.value)) | ||
end | ||
end | ||
end | ||
end | ||
|
||
# Miscellaneous lifted operators | ||
# Binary operators | ||
|
||
function Base.abs{T}(x::Nullable{T}) | ||
if isbits(T) | ||
return Nullable(abs(x.value), x.isnull) | ||
else | ||
error() | ||
end | ||
end | ||
|
||
function Base.abs2{T}(x::Nullable{T}) | ||
if isbits(T) | ||
return Nullable(abs2(x.value), x.isnull) | ||
else | ||
error() | ||
# Note this list does not include ^, ÷ and % | ||
# Operations between signed and unsigned types are not safe: promotion to unsigned | ||
# gives an InexactError for negative numbers | ||
if VERSION >= v"0.5.0-dev" | ||
for op in (:+, :-, :*, :/, :&, :|, :<<, :>>, :(>>>), | ||
:(==), :<, :>, :<=, :>=, | ||
:scalarmin, :scalarmax) | ||
@eval begin | ||
# to fix ambiguities | ||
null_safe_op{S<:SafeFloats, | ||
T<:SafeFloats}(::typeof($op), ::Type{S}, ::Type{T}) = true | ||
null_safe_op{S<:SafeSigned, | ||
T<:SafeSigned}(::typeof($op), ::Type{S}, ::Type{T}) = true | ||
null_safe_op{S<:SafeUnsigned, | ||
T<:SafeUnsigned}(::typeof($op), ::Type{S}, ::Type{T}) = true | ||
end | ||
end | ||
end | ||
|
||
function Base.sqrt{T}(x::Nullable{T}) | ||
if isbits(T) | ||
return Nullable(sqrt(x.value), x.isnull) | ||
else | ||
error() | ||
else # No functors for all methods on 0.4: use the slow path for missing ones | ||
for op in (:+, :-, :*, :/, :&, :|, | ||
:<, :>, | ||
:scalarmin, :scalarmax) | ||
@eval begin | ||
# to fix ambiguities | ||
null_safe_op{S<:SafeFloats, | ||
T<:SafeFloats}(::typeof(@functorize($op)), ::Type{S}, ::Type{T}) = true | ||
null_safe_op{S<:SafeSigned, | ||
T<:SafeSigned}(::typeof(@functorize($op)), ::Type{S}, ::Type{T}) = true | ||
null_safe_op{S<:SafeUnsigned, | ||
T<:SafeUnsigned}(::typeof(@functorize($op)), ::Type{S}, ::Type{T}) = true | ||
end | ||
end | ||
end | ||
|
||
## Lifted functors | ||
|
||
@compat function (::typeof(@functorize(scalarmin))){S1, S2}(x::Nullable{S1}, y::Nullable{S2}) | ||
if isbits(S1) & isbits(S2) | ||
return Nullable(Base.scalarmin(x.value, y.value), x.isnull | y.isnull) | ||
else | ||
error() | ||
end | ||
end | ||
@compat function (::typeof(@functorize(scalarmax))){S1, S2}(x::Nullable{S1}, y::Nullable{S2}) | ||
if isbits(S1) & isbits(S2) | ||
return Nullable(Base.scalarmax(x.value, y.value), x.isnull | y.isnull) | ||
else | ||
error() | ||
for op in (:+, :-, :*, :/, :%, :÷, :&, :|, :^, :<<, :>>, :(>>>), | ||
:(==), :<, :>, :<=, :>=, | ||
:scalarmin, :scalarmax) | ||
@eval begin | ||
@inline function $op{S,T}(x::Nullable{S}, y::Nullable{T}) | ||
R = promote_op(@functorize($op), S, T) | ||
if null_safe_op(@functorize($op), S, T) | ||
Nullable{R}($op(x.value, y.value), x.isnull | y.isnull) | ||
else | ||
(x.isnull | y.isnull) ? Nullable{R}() : | ||
Nullable{R}($op(x.value, y.value)) | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.
be more specific with these?