Skip to content
This repository has been archived by the owner on May 4, 2019. It is now read-only.

Commit

Permalink
Improve operators
Browse files Browse the repository at this point in the history
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
nalimilan committed Jul 3, 2016
1 parent 383661a commit 51e3a44
Show file tree
Hide file tree
Showing 2 changed files with 258 additions and 147 deletions.
201 changes: 104 additions & 97 deletions src/operators.jl
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.

Copy link
@tkelman

tkelman Jul 3, 2016

Contributor

be more specific with these?

This comment has been minimized.

Copy link
@nalimilan

nalimilan Jul 3, 2016

Author Member

Good idea. I've pushed it as 5ed6f7c.

This comment has been minimized.

Copy link
@tkelman

tkelman Jul 3, 2016

Contributor

Thanks! There are a few other mentions of 0.5.0-dev where it would be good to do the same.

This comment has been minimized.

Copy link
@nalimilan

nalimilan Jul 3, 2016

Author Member

I'm afraid it would be painful to do the same for functors. IIRC, changes have been introduced in several steps. It's also old enough that I'm not sure it's worth it.

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
Loading

0 comments on commit 51e3a44

Please sign in to comment.