Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Override ==, any() and all() for AbstractArray{:>Null} #37

Merged
merged 1 commit into from
Oct 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions REQUIRE
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
julia 0.6
Compat 0.31
56 changes: 56 additions & 0 deletions src/Nulls.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ __precompile__(true)
module Nulls

import Base: *, <, ==, !=, <=, !, +, -, ^, /, &, |, xor
using Compat: AbstractRange

export null, nulls, Null

Expand Down Expand Up @@ -150,4 +151,59 @@ julia> coalesce.([null, 1, null], [0, 10, 5])
coalesce(x) = x
coalesce(x, y...) = ifelse(x !== null, x, coalesce(y...))

# AbstractArray{>:Null} functions

function ==(A::AbstractArray{>:Null}, B::AbstractArray)
if indices(A) != indices(B)
return false
end
if isa(A,AbstractRange) != isa(B,AbstractRange)
return false
end
anynull = false
@inbounds for (a, b) in zip(A, B)
eq = (a == b)
if eq === false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't have thought to use === for checking booleans before seeing this, but it's noticably faster

julia> @benchmark false === false
BenchmarkTools.Trial:
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     0.038 ns (0.00% GC)
  median time:      0.048 ns (0.00% GC)
  mean time:        0.052 ns (0.00% GC)
  maximum time:     14.335 ns (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1000

julia> @benchmark false == false
BenchmarkTools.Trial:
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     2.353 ns (0.00% GC)
  median time:      2.958 ns (0.00% GC)
  mean time:        3.835 ns (0.00% GC)
  maximum time:     4.157 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1000

is there any reason to not always use === rather than == for boolean comparison?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any reason to not always use === rather than == for boolean comparison?

In general, boolean comparisons aren't that common, especially in control flow, since it's typically easier to just say if eq or if !eq. In this case === is needed to ensure that null doesn't propagate in the comparison.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't trust benchmarks on elementary operations like this. Wrapping these in a function gives exactly the same native code.

I could also have used isequal(eq, true), but using === we ensure no other type can override isequal, as "truthiness" isn't considered as a good idea in general in Base Julia.

return false
else
anynull |= isnull(eq)
end
end
return anynull ? null : true
end

==(A::AbstractArray, B::AbstractArray{>:Null}) = (B == A)
==(A::AbstractArray{>:Null}, B::AbstractArray{>:Null}) =
invoke(==, Tuple{AbstractArray{>:Null}, AbstractArray}, A, B)

!=(x::AbstractArray{>:Null}, y::AbstractArray) = !(x == y)
!=(x::AbstractArray, y::AbstractArray{>:Null}) = !(x == y)
!=(x::AbstractArray{>:Null}, y::AbstractArray{>:Null}) = !(x == y)

function Base.any(f, A::AbstractArray{>:Null})
anynull = false
@inbounds for x in A
v = f(x)
if v === true
return true
else
anynull |= isnull(v)
end
end
return anynull ? null : false
end

function Base.all(f, A::AbstractArray{>:Null})
anynull = false
@inbounds for x in A
v = f(x)
if v === false
return false
else
anynull |= isnull(v)
end
end
return anynull ? null : true
end

end # module
44 changes: 44 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,48 @@ using Base.Test, Nulls
@test convert(Union{Int, Null}, 1.0) == 1

@test Nulls.T(Any) == Any

# AbstractArray{>:Null}

@test isnull([1, null] == [1, null])
@test isnull(["a", null] == ["a", null])
@test isnull(Any[1, null] == Any[1, null])
@test isnull(Any[null] == Any[null])
@test isnull([null] == [null])
@test isnull(Any[null, 2] == Any[1, null])
@test isnull([null, false] == BitArray([true, false]))
@test isnull(Any[null, false] == BitArray([true, false]))
@test Union{Int, Null}[1] == Union{Float64, Null}[1.0]
@test Union{Int, Null}[1] == [1.0]
@test Union{Bool, Null}[true] == BitArray([true])
@test !(Union{Int, Null}[1] == [2])
@test !([1] == Union{Int, Null}[2])
@test !(Union{Int, Null}[1] == Union{Int, Null}[2])

@test isnull([1, null] != [1, null])
@test isnull(["a", null] != ["a", null])
@test isnull(Any[1, null] != Any[1, null])
@test isnull(Any[null] != Any[null])
@test isnull([null] != [null])
@test isnull(Any[null, 2] != Any[1, null])
@test isnull([null, false] != BitArray([true, false]))
@test isnull(Any[null, false] != BitArray([true, false]))
@test !(Union{Int, Null}[1] != Union{Float64, Null}[1.0])
@test !(Union{Int, Null}[1] != [1.0])
@test !(Union{Bool, Null}[true] != BitArray([true]))
@test Union{Int, Null}[1] != [2]
@test [1] != Union{Int, Null}[2]
@test Union{Int, Null}[1] != Union{Int, Null}[2]

@test any([true, null])
@test any(x -> x == 1, [1, null])
@test isnull(any([false, null]))
@test isnull(any(x -> x == 1, [2, null]))
@test isnull(all([true, null]))
@test isnull(all(x -> x == 1, [1, null]))
@test !all([false, null])
@test !all(x -> x == 1, [2, null])
@test 1 in [1, null]
@test isnull(2 in [1, null])
@test isnull(null in [1, null])
end