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

Commit

Permalink
Merge pull request #119 from JuliaStats/nl/ops
Browse files Browse the repository at this point in the history
Improve operators
  • Loading branch information
nalimilan authored Jul 3, 2016
2 parents 383661a + b783922 commit c0f4d6a
Show file tree
Hide file tree
Showing 2 changed files with 289 additions and 147 deletions.
205 changes: 108 additions & 97 deletions src/operators.jl
Original file line number Diff line number Diff line change
@@ -1,121 +1,132 @@
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"
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
$op(x::Nullable{Union{}}) = Nullable()
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
$op(x::Nullable{Union{}}, y::Nullable{Union{}}) = Nullable()
$op{S}(x::Nullable{Union{}}, y::Nullable{S}) = Nullable{S}()
$op{S}(x::Nullable{S}, y::Nullable{Union{}}) = Nullable{S}()
end
end
Loading

0 comments on commit c0f4d6a

Please sign in to comment.