Skip to content

Commit

Permalink
Use ismissing(x) instead of x === missing (JuliaLang#44407)
Browse files Browse the repository at this point in the history
This is more generic and will allow packages to define custom
`missing`-like types allowing to distinguish several kinds of
missing values like in e.g. Stata and SAS (see
https://github.com/nalimilan/TypedMissings.jl). This should have no
performance impact now thanks to JuliaLang#38905.
  • Loading branch information
nalimilan committed Jan 7, 2024
1 parent 05992e7 commit 1d3dd85
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 17 deletions.
34 changes: 17 additions & 17 deletions base/missing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ function iterate(itr::SkipMissing, state...)
y = iterate(itr.x, state...)
y === nothing && return nothing
item, state = y
while item === missing
while ismissing(item)
y = iterate(itr.x, state)
y === nothing && return nothing
item, state = y
Expand All @@ -251,12 +251,12 @@ end

IndexStyle(::Type{<:SkipMissing{T}}) where {T} = IndexStyle(T)
eachindex(itr::SkipMissing) =
Iterators.filter(i -> @inbounds(itr.x[i]) !== missing, eachindex(itr.x))
Iterators.filter(i -> !ismissing(@inbounds(itr.x[i])), eachindex(itr.x))
keys(itr::SkipMissing) =
Iterators.filter(i -> @inbounds(itr.x[i]) !== missing, keys(itr.x))
Iterators.filter(i -> !ismissing(@inbounds(itr.x[i])), keys(itr.x))
@propagate_inbounds function getindex(itr::SkipMissing, I...)
v = itr.x[I...]
v === missing && throw(MissingException(LazyString("the value at index ", I, " is missing")))
ismissing(v) && throw(MissingException(LazyString("the value at index ", I, " is missing")))
v
end

Expand All @@ -280,18 +280,18 @@ function _mapreduce(f, op, ::IndexLinear, itr::SkipMissing{<:AbstractArray})
ilast = last(inds)
for outer i in i:ilast
@inbounds ai = A[i]
ai !== missing && break
!ismissing(ai) && break
end
ai === missing && return mapreduce_empty(f, op, eltype(itr))
ismissing(ai) && return mapreduce_empty(f, op, eltype(itr))
a1::eltype(itr) = ai
i == typemax(typeof(i)) && return mapreduce_first(f, op, a1)
i += 1
ai = missing
for outer i in i:ilast
@inbounds ai = A[i]
ai !== missing && break
!ismissing(ai) && break
end
ai === missing && return mapreduce_first(f, op, a1)
ismissing(ai) && return mapreduce_first(f, op, a1)
# We know A contains at least two non-missing entries: the result cannot be nothing
something(mapreduce_impl(f, op, itr, first(inds), last(inds)))
end
Expand All @@ -309,7 +309,7 @@ mapreduce_impl(f, op, A::SkipMissing, ifirst::Integer, ilast::Integer) =
return nothing
elseif ifirst == ilast
@inbounds a1 = A[ifirst]
if a1 === missing
if ismissing(a1)
return nothing
else
return Some(mapreduce_first(f, op, a1))
Expand All @@ -320,25 +320,25 @@ mapreduce_impl(f, op, A::SkipMissing, ifirst::Integer, ilast::Integer) =
i = ifirst
for outer i in i:ilast
@inbounds ai = A[i]
ai !== missing && break
!ismissing(ai) && break
end
ai === missing && return nothing
ismissing(ai) && return nothing
a1 = ai::eltype(itr)
i == typemax(typeof(i)) && return Some(mapreduce_first(f, op, a1))
i += 1
ai = missing
for outer i in i:ilast
@inbounds ai = A[i]
ai !== missing && break
!ismissing(ai) && break
end
ai === missing && return Some(mapreduce_first(f, op, a1))
ismissing(ai) && return Some(mapreduce_first(f, op, a1))
a2 = ai::eltype(itr)
i == typemax(typeof(i)) && return Some(op(f(a1), f(a2)))
i += 1
v = op(f(a1), f(a2))
@simd for i = i:ilast
@inbounds ai = A[i]
if ai !== missing
if !ismissing(ai)
v = op(v, f(ai))
end
end
Expand Down Expand Up @@ -384,7 +384,7 @@ julia> filter(isodd, skipmissing(x))
function filter(f, itr::SkipMissing{<:AbstractArray})
y = similar(itr.x, eltype(itr), 0)
for xi in itr.x
if xi !== missing && f(xi)
if !ismissing(xi) && f(xi)
push!(y, xi)
end
end
Expand Down Expand Up @@ -450,7 +450,7 @@ ERROR: `b` is still missing
macro coalesce(args...)
expr = :(missing)
for arg in reverse(args)
expr = :((val = $arg) !== missing ? val : $expr)
expr = :(!ismissing((val = $(esc(arg));)) ? val : $expr)
end
return esc(:(let val; $expr; end))
return :(let val; $expr; end)
end
26 changes: 26 additions & 0 deletions test/missing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -651,3 +651,29 @@ for func in (round, ceil, floor, trunc)
@test Core.Compiler.is_foldable(Base.infer_effects(func, (Type{Int},Union{Int,Missing})))
end
end

@testset "Custom Missing type" begin
struct NewMissing end
Base.ismissing(::NewMissing) = true
Base.coalesce(x::NewMissing, y...) = coalesce(y...)
Base.isless(::NewMissing, ::NewMissing) = false
Base.isless(::NewMissing, ::Any) = false
Base.isless(::Any, ::NewMissing) = true
Base.isequal(::NewMissing, ::Missing) = true
Base.isequal(::Missing, ::NewMissing) = true
arr = [missing 1 2 3 missing 10 11 12 missing]
newarr = Union{Int, NewMissing}[ismissing(v) ? NewMissing() : v for v in arr]

@test all(skipmissing(arr) .== skipmissing(newarr))
@test all(eachindex(skipmissing(arr)) .== eachindex(skipmissing(newarr)))
@test all(keys(skipmissing(arr)) .== keys(skipmissing(newarr)))
@test_broken sum(skipmissing(arr)) == sum(skipmissing(newarr))
@test filter(>(10), skipmissing(arr)) == filter(>(10), skipmissing(newarr))
@test isequal(sort(vec(arr)), sort(vec(newarr)))

@test_throws MissingException skipmissing(newarr)[findfirst(ismissing, newarr)]
@test coalesce(NewMissing(), 1) == coalesce(NewMissing(), NewMissing(), 1) == 1
@test coalesce(NewMissing()) === coalesce(NewMissing(), NewMissing()) === missing
@test @coalesce(NewMissing(), 1) == @coalesce(NewMissing(), NewMissing(), 1) == 1
@test @coalesce(NewMissing()) === @coalesce(NewMissing(), NewMissing()) === missing
end

0 comments on commit 1d3dd85

Please sign in to comment.