Skip to content

Commit

Permalink
Add missing and Missing
Browse files Browse the repository at this point in the history
Add basic support with special methods for operators and standard math functions.
Adapt ==(::AbstractArray, ::AbstractArray), all() and any() to support
three-valued logic. This requires defining missing and Missing early in the
bootstrap process, but other missing-related code is included relatively late
to be able to add methods to Base functions defined in various places.
  • Loading branch information
nalimilan committed Nov 20, 2017
1 parent 8085047 commit 96eb33a
Show file tree
Hide file tree
Showing 12 changed files with 443 additions and 48 deletions.
8 changes: 6 additions & 2 deletions base/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1554,12 +1554,16 @@ function (==)(A::AbstractArray, B::AbstractArray)
if isa(A,AbstractRange) != isa(B,AbstractRange)
return false
end
anymissing = false
for (a, b) in zip(A, B)
if !(a == b)
eq = (a == b)
if ismissing(eq)
anymissing = true
elseif !eq
return false
end
end
return true
return anymissing ? missing : true
end

# sub2ind and ind2sub
Expand Down
23 changes: 23 additions & 0 deletions base/essentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -751,3 +751,26 @@ This function simply returns its argument by default, since the elements
of a general iterator are normally considered its "values".
"""
values(itr) = itr

"""
Missing
A type with no fields whose singleton instance [`missing`](@ref) is used
to represent missing values.
"""
struct Missing end

"""
missing
The singleton instance of type [`Missing`](@ref) representing a missing value.
"""
const missing = Missing()

"""
ismissing(x)
Indicate whether `x` is [`missing`](@ref).
"""
ismissing(::Any) = false
ismissing(::Missing) = true
6 changes: 6 additions & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export
Irrational,
Matrix,
MergeSort,
Missing,
NTuple,
Nullable,
ObjectIdDict,
Expand Down Expand Up @@ -150,6 +151,7 @@ export
EOFError,
InvalidStateException,
KeyError,
MissingException,
NullException,
ParseError,
SystemError,
Expand Down Expand Up @@ -882,6 +884,10 @@ export
isready,
fetch,

# missing values
ismissing,
missing,

# time
sleep,
time,
Expand Down
115 changes: 115 additions & 0 deletions base/missing.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

# Missing, missing and ismissing are defined in essentials.jl

show(io::IO, x::Missing) = print(io, "missing")

"""
MissingException(msg)
Exception thrown when a [`missing`](@ref) value is encountered in a situation
where it is not supported. The error message, in the `msg` field
may provide more specific details.
"""
struct MissingException <: Exception
msg::AbstractString
end

showerror(io::IO, ex::MissingException) =
print(io, "MissingException: ", ex.msg)

promote_rule(::Type{Missing}, ::Type{T}) where {T} = Union{T, Missing}
promote_rule(::Type{Union{S,Missing}}, ::Type{T}) where {T,S} = Union{promote_type(T, S), Missing}
promote_rule(::Type{Any}, ::Type{T}) where {T} = Any
promote_rule(::Type{Any}, ::Type{Missing}) = Any
promote_rule(::Type{Missing}, ::Type{Any}) = Any
promote_rule(::Type{Missing}, ::Type{Missing}) = Missing

convert(::Type{Union{T, Missing}}, x) where {T} = convert(T, x)
# To print more appropriate message than "T not defined"
convert(::Type{Missing}, x) = throw(MethodError(convert, (Missing, x)))
convert(::Type{Missing}, ::Missing) = missing

# Comparison operators
==(::Missing, ::Missing) = missing
==(::Missing, ::Any) = missing
==(::Any, ::Missing) = missing
# To fix ambiguity
==(::Missing, ::WeakRef) = missing
==(::WeakRef, ::Missing) = missing
isequal(::Missing, ::Missing) = true
isequal(::Missing, ::Any) = false
isequal(::Any, ::Missing) = false
<(::Missing, ::Missing) = missing
<(::Missing, ::Any) = missing
<(::Any, ::Missing) = missing
isless(::Missing, ::Missing) = false
isless(::Missing, ::Any) = false
isless(::Any, ::Missing) = true

# Unary operators/functions
for f in (:(!), :(+), :(-), :(identity), :(zero),
:(abs), :(abs2), :(sign),
:(acos), :(acosh), :(asin), :(asinh), :(atan), :(atanh),
:(sin), :(sinh), :(cos), :(cosh), :(tan), :(tanh),
:(exp), :(exp2), :(expm1), :(log), :(log10), :(log1p),
:(log2), :(exponent), :(sqrt), :(gamma), :(lgamma),
:(iseven), :(ispow2), :(isfinite), :(isinf), :(isodd),
:(isinteger), :(isreal), :(isnan), :(isempty),
:(iszero), :(transpose), :(float))
@eval Math.$(f)(::Missing) = missing
end

zero(::Type{Union{T, Missing}}) where {T} = zero(T)
# To prevent StackOverflowError
zero(::Type{Any}) = throw(MethodError(zero, (Any,)))

# Binary operators/functions
for f in (:(+), :(-), :(*), :(/), :(^),
:(div), :(mod), :(fld), :(rem), :(min), :(max))
@eval begin
# Scalar with missing
($f)(::Missing, ::Missing) = missing
($f)(d::Missing, x::Number) = missing
($f)(d::Number, x::Missing) = missing
end
end

# Rounding and related functions
for f in (:(ceil), :(floor), :(round), :(trunc))
@eval begin
($f)(::Missing, digits::Integer=0, base::Integer=0) = missing
($f)(::Type{>:Missing}, ::Missing) = missing
($f)(::Type{T}, ::Missing) where {T} =
throw(MissingException("cannot convert a missing value to type $T"))
end
end

# to avoid ambiguity warnings
(^)(::Missing, ::Integer) = missing

# Bit operators
(&)(::Missing, ::Missing) = missing
(&)(a::Missing, b::Bool) = ifelse(b, missing, false)
(&)(b::Bool, a::Missing) = ifelse(b, missing, false)
(&)(::Missing, ::Integer) = missing
(&)(::Integer, ::Missing) = missing
(|)(::Missing, ::Missing) = missing
(|)(a::Missing, b::Bool) = ifelse(b, true, missing)
(|)(b::Bool, a::Missing) = ifelse(b, true, missing)
(|)(::Missing, ::Integer) = missing
(|)(::Integer, ::Missing) = missing
xor(::Missing, ::Missing) = missing
xor(a::Missing, b::Bool) = missing
xor(b::Bool, a::Missing) = missing
xor(::Missing, ::Integer) = missing
xor(::Integer, ::Missing) = missing

*(d::Missing, x::AbstractString) = missing
*(d::AbstractString, x::Missing) = missing

function float(A::AbstractArray{Union{T, Missing}}) where {T}
U = typeof(float(zero(T)))
convert(AbstractArray{Union{U, Missing}}, A)
end
float(A::AbstractArray{Missing}) = A
2 changes: 1 addition & 1 deletion base/operators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ julia> "foo" ≠ "foo"
false
```
"""
!=(x, y) = !(x == y)::Bool
!=(x, y) = !(x == y)
const = !=

"""
Expand Down
24 changes: 20 additions & 4 deletions base/reduce.jl
Original file line number Diff line number Diff line change
Expand Up @@ -610,10 +610,16 @@ true
```
"""
function any(f, itr)
anymissing = false
for x in itr
f(x) && return true
v = f(x)
if ismissing(v)
anymissing = true
elseif v
return true
end
end
return false
return anymissing ? missing : false
end

"""
Expand All @@ -635,12 +641,22 @@ false
```
"""
function all(f, itr)
anymissing = false
for x in itr
f(x) || return false
v = f(x)
if ismissing(v)
anymissing = true
# this syntax allows throwing a TypeError for non-Bool, for consistency with any
elseif v
continue
else
return false
end
end
return true
return anymissing ? missing : true
end


## in & contains

"""
Expand Down
2 changes: 1 addition & 1 deletion base/reducedim.jl
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ function reducedim_init(f, op::typeof(*), A::AbstractArray, region)
end
function _reducedim_init(f, op, fv, fop, A, region)
T = promote_union(eltype(A))
if applicable(zero, T)
if T !== Any && applicable(zero, T)
x = f(zero(T))
z = op(fv(x), fv(x))
Tr = typeof(z) == typeof(x) && !isbits(T) ? T : typeof(z)
Expand Down
3 changes: 3 additions & 0 deletions base/sysimg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,9 @@ const × = cross
# statistics
include("statistics.jl")

# missing values
include("missing.jl")

# libgit2 support
include("libgit2/libgit2.jl")

Expand Down
4 changes: 4 additions & 0 deletions test/ambiguous.jl
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,10 @@ end
pop!(need_to_handle_undef_sparam, which(Base.SparseArrays._absspvec_vcat, (AbstractSparseArray{Tv, Ti, 1} where {Tv, Ti},)))
pop!(need_to_handle_undef_sparam, which(Base.SparseArrays._absspvec_hcat, (AbstractSparseArray{Tv, Ti, 1} where {Tv, Ti},)))
pop!(need_to_handle_undef_sparam, which(Base.cat, (Any, Base.SparseArrays._TypedDenseConcatGroup{T} where T)))
pop!(need_to_handle_undef_sparam, which(Base.float, Tuple{AbstractArray{Union{Missing, T},N} where {T, N}}))
pop!(need_to_handle_undef_sparam, which(Base.convert, Tuple{Type{Union{Missing, T}} where T, Any}))
pop!(need_to_handle_undef_sparam, which(Base.promote_rule, Tuple{Type{Union{Missing, S}} where S, Type{T} where T}))
pop!(need_to_handle_undef_sparam, which(Base.zero, Tuple{Type{Union{Missing, T}} where T}))
@test need_to_handle_undef_sparam == Set()
end
end
Expand Down
2 changes: 1 addition & 1 deletion test/choosetests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function choosetests(choices = [])
"checked", "bitset", "floatfuncs", "compile", "distributed", "inline",
"boundscheck", "error", "ambiguous", "cartesian", "asmvariant", "osutils",
"channels", "iostream", "specificity", "codegen", "codevalidation",
"reinterpretarray", "syntax"
"reinterpretarray", "syntax", "missing"
]

if isdir(joinpath(JULIA_HOME, Base.DOCDIR, "examples"))
Expand Down
Loading

0 comments on commit 96eb33a

Please sign in to comment.