Skip to content

Commit b207fc8

Browse files
committed
Add missing and Missing
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.
1 parent 8085047 commit b207fc8

File tree

11 files changed

+443
-47
lines changed

11 files changed

+443
-47
lines changed

base/abstractarray.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,12 +1554,16 @@ function (==)(A::AbstractArray, B::AbstractArray)
15541554
if isa(A,AbstractRange) != isa(B,AbstractRange)
15551555
return false
15561556
end
1557+
anymissing = false
15571558
for (a, b) in zip(A, B)
1558-
if !(a == b)
1559+
eq = (a == b)
1560+
if ismissing(eq)
1561+
anymissing = true
1562+
elseif !eq
15591563
return false
15601564
end
15611565
end
1562-
return true
1566+
return anymissing ? missing : true
15631567
end
15641568

15651569
# sub2ind and ind2sub

base/essentials.jl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,3 +751,26 @@ This function simply returns its argument by default, since the elements
751751
of a general iterator are normally considered its "values".
752752
"""
753753
values(itr) = itr
754+
755+
"""
756+
Missing
757+
758+
A type with no fields whose singleton instance [`missing`](@ref) is used
759+
to represent missing values.
760+
"""
761+
struct Missing end
762+
763+
"""
764+
missing
765+
766+
The singleton instance of type [`Missing`](@ref) representing a missing value.
767+
"""
768+
const missing = Missing()
769+
770+
"""
771+
ismissing(x)
772+
773+
Indicate whether `x` is [`missing`](@ref).
774+
"""
775+
ismissing(::Any) = false
776+
ismissing(::Missing) = true

base/exports.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export
7676
Irrational,
7777
Matrix,
7878
MergeSort,
79+
Missing,
7980
NTuple,
8081
Nullable,
8182
ObjectIdDict,
@@ -150,6 +151,7 @@ export
150151
EOFError,
151152
InvalidStateException,
152153
KeyError,
154+
MissingException,
153155
NullException,
154156
ParseError,
155157
SystemError,
@@ -882,6 +884,10 @@ export
882884
isready,
883885
fetch,
884886

887+
# missing values
888+
ismissing,
889+
missing,
890+
885891
# time
886892
sleep,
887893
time,

base/missing.jl

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# This file is a part of Julia. License is MIT: https://julialang.org/license
2+
3+
# Missing, missing and ismissing are defined in essentials.jl
4+
5+
show(io::IO, x::Missing) = print(io, "missing")
6+
7+
"""
8+
MissingException(msg)
9+
10+
Exception thrown when a [`missing`](@ref) value is encountered in a situation
11+
where it is not supported. The error message, in the `msg` field
12+
may provide more specific details.
13+
"""
14+
struct MissingException <: Exception
15+
msg::AbstractString
16+
end
17+
18+
showerror(io::IO, ex::MissingException) =
19+
print(io, "MissingException: ", ex.msg)
20+
21+
promote_rule(::Type{Missing}, ::Type{T}) where {T} = Union{T, Missing}
22+
promote_rule(::Type{Union{S,Missing}}, ::Type{T}) where {T,S} = Union{promote_type(T, S), Missing}
23+
promote_rule(::Type{Any}, ::Type{T}) where {T} = Any
24+
promote_rule(::Type{Any}, ::Type{Missing}) = Any
25+
promote_rule(::Type{Missing}, ::Type{Any}) = Any
26+
promote_rule(::Type{Missing}, ::Type{Missing}) = Missing
27+
28+
convert(::Type{Union{T, Missing}}, x) where {T} = convert(T, x)
29+
# To print more appropriate message than "T not defined"
30+
convert(::Type{Missing}, x) = throw(MethodError(convert, (Missing, x)))
31+
convert(::Type{Missing}, ::Missing) = missing
32+
33+
# Comparison operators
34+
==(::Missing, ::Missing) = missing
35+
==(::Missing, ::Any) = missing
36+
==(::Any, ::Missing) = missing
37+
# To fix ambiguity
38+
==(::Missing, ::WeakRef) = missing
39+
==(::WeakRef, ::Missing) = missing
40+
isequal(::Missing, ::Missing) = true
41+
isequal(::Missing, ::Any) = false
42+
isequal(::Any, ::Missing) = false
43+
<(::Missing, ::Missing) = missing
44+
<(::Missing, ::Any) = missing
45+
<(::Any, ::Missing) = missing
46+
isless(::Missing, ::Missing) = false
47+
isless(::Missing, ::Any) = false
48+
isless(::Any, ::Missing) = true
49+
50+
# Unary operators/functions
51+
for f in (:(!), :(+), :(-), :(identity), :(zero),
52+
:(abs), :(abs2), :(sign),
53+
:(acos), :(acosh), :(asin), :(asinh), :(atan), :(atanh),
54+
:(sin), :(sinh), :(cos), :(cosh), :(tan), :(tanh),
55+
:(exp), :(exp2), :(expm1), :(log), :(log10), :(log1p),
56+
:(log2), :(exponent), :(sqrt), :(gamma), :(lgamma),
57+
:(iseven), :(ispow2), :(isfinite), :(isinf), :(isodd),
58+
:(isinteger), :(isreal), :(isnan), :(isempty),
59+
:(iszero), :(transpose), :(float))
60+
@eval Math.$(f)(::Missing) = missing
61+
end
62+
63+
zero(::Type{Union{T, Missing}}) where {T} = zero(T)
64+
# To prevent StackOverflowError
65+
zero(::Type{Any}) = throw(MethodError(zero, (Any,)))
66+
67+
# Binary operators/functions
68+
for f in (:(+), :(-), :(*), :(/), :(^),
69+
:(div), :(mod), :(fld), :(rem), :(min), :(max))
70+
@eval begin
71+
# Scalar with missing
72+
($f)(::Missing, ::Missing) = missing
73+
($f)(d::Missing, x::Number) = missing
74+
($f)(d::Number, x::Missing) = missing
75+
end
76+
end
77+
78+
# Rounding and related functions
79+
for f in (:(ceil), :(floor), :(round), :(trunc))
80+
@eval begin
81+
($f)(::Missing, digits::Integer=0, base::Integer=0) = missing
82+
($f)(::Type{>:Missing}, ::Missing) = missing
83+
($f)(::Type{T}, ::Missing) where {T} =
84+
throw(MissingException("cannot convert a missing value to type $T"))
85+
end
86+
end
87+
88+
# to avoid ambiguity warnings
89+
(^)(::Missing, ::Integer) = missing
90+
91+
# Bit operators
92+
(&)(::Missing, ::Missing) = missing
93+
(&)(a::Missing, b::Bool) = ifelse(b, missing, false)
94+
(&)(b::Bool, a::Missing) = ifelse(b, missing, false)
95+
(&)(::Missing, ::Integer) = missing
96+
(&)(::Integer, ::Missing) = missing
97+
(|)(::Missing, ::Missing) = missing
98+
(|)(a::Missing, b::Bool) = ifelse(b, true, missing)
99+
(|)(b::Bool, a::Missing) = ifelse(b, true, missing)
100+
(|)(::Missing, ::Integer) = missing
101+
(|)(::Integer, ::Missing) = missing
102+
xor(::Missing, ::Missing) = missing
103+
xor(a::Missing, b::Bool) = missing
104+
xor(b::Bool, a::Missing) = missing
105+
xor(::Missing, ::Integer) = missing
106+
xor(::Integer, ::Missing) = missing
107+
108+
*(d::Missing, x::AbstractString) = missing
109+
*(d::AbstractString, x::Missing) = missing
110+
111+
function float(A::AbstractArray{Union{T, Missing}}) where {T}
112+
U = typeof(float(zero(T)))
113+
convert(AbstractArray{Union{U, Missing}}, A)
114+
end
115+
float(A::AbstractArray{Missing}) = A

base/operators.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ julia> "foo" ≠ "foo"
155155
false
156156
```
157157
"""
158-
!=(x, y) = !(x == y)::Bool
158+
!=(x, y) = !(x == y)
159159
const = !=
160160

161161
"""

base/reduce.jl

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -610,10 +610,16 @@ true
610610
```
611611
"""
612612
function any(f, itr)
613+
anymissing = false
613614
for x in itr
614-
f(x) && return true
615+
v = f(x)
616+
if ismissing(v)
617+
anymissing = true
618+
elseif v
619+
return true
620+
end
615621
end
616-
return false
622+
return anymissing ? missing : false
617623
end
618624

619625
"""
@@ -635,12 +641,22 @@ false
635641
```
636642
"""
637643
function all(f, itr)
644+
anymissing = false
638645
for x in itr
639-
f(x) || return false
646+
v = f(x)
647+
if ismissing(v)
648+
anymissing = true
649+
# this syntax allows throwing a TypeError for non-Bool, for consistency with any
650+
elseif v
651+
continue
652+
else
653+
return false
654+
end
640655
end
641-
return true
656+
return anymissing ? missing : true
642657
end
643658

659+
644660
## in & contains
645661

646662
"""

base/sysimg.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,9 @@ const × = cross
394394
# statistics
395395
include("statistics.jl")
396396

397+
# missing values
398+
include("missing.jl")
399+
397400
# libgit2 support
398401
include("libgit2/libgit2.jl")
399402

test/ambiguous.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,10 @@ end
280280
pop!(need_to_handle_undef_sparam, which(Base.SparseArrays._absspvec_vcat, (AbstractSparseArray{Tv, Ti, 1} where {Tv, Ti},)))
281281
pop!(need_to_handle_undef_sparam, which(Base.SparseArrays._absspvec_hcat, (AbstractSparseArray{Tv, Ti, 1} where {Tv, Ti},)))
282282
pop!(need_to_handle_undef_sparam, which(Base.cat, (Any, Base.SparseArrays._TypedDenseConcatGroup{T} where T)))
283+
pop!(need_to_handle_undef_sparam, which(Base.float, Tuple{AbstractArray{Union{Missing, T},N} where {T, N}}))
284+
pop!(need_to_handle_undef_sparam, which(Base.convert, Tuple{Type{Union{Missing, T}} where T, Any}))
285+
pop!(need_to_handle_undef_sparam, which(Base.promote_rule, Tuple{Type{Union{Missing, S}} where S, Type{T} where T}))
286+
pop!(need_to_handle_undef_sparam, which(Base.zero, Tuple{Type{Union{Missing, T}} where T}))
283287
@test need_to_handle_undef_sparam == Set()
284288
end
285289
end

test/choosetests.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ function choosetests(choices = [])
4949
"checked", "bitset", "floatfuncs", "compile", "distributed", "inline",
5050
"boundscheck", "error", "ambiguous", "cartesian", "asmvariant", "osutils",
5151
"channels", "iostream", "specificity", "codegen", "codevalidation",
52-
"reinterpretarray", "syntax"
52+
"reinterpretarray", "syntax", "missing"
5353
]
5454

5555
if isdir(joinpath(JULIA_HOME, Base.DOCDIR, "examples"))

0 commit comments

Comments
 (0)