Skip to content

Commit c070ae1

Browse files
nalimilanevetion
authored andcommitted
Add missing and Missing (JuliaLang#24653)
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. Add new manual section about missing values.
1 parent 6018887 commit c070ae1

File tree

20 files changed

+850
-53
lines changed

20 files changed

+850
-53
lines changed

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ New language features
3131
`@generated` and normal implementations of part of a function. Surrounding code
3232
will be common to both versions ([#23168]).
3333

34+
* The `missing` singleton object (of type `Missing`) has been added to represent
35+
missing values ([#24653]). It propagates through standard operators and mathematical functions,
36+
and implements three-valued logic, similar to SQLs `NULL` and R's `NA`.
37+
3438
Language changes
3539
----------------
3640

@@ -1700,3 +1704,4 @@ Command-line option changes
17001704
[#24320]: https://github.com/JuliaLang/julia/issues/24320
17011705
[#24396]: https://github.com/JuliaLang/julia/issues/24396
17021706
[#24413]: https://github.com/JuliaLang/julia/issues/24413
1707+
[#24653]: https://github.com/JuliaLang/julia/issues/24653

base/abstractarray.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1572,12 +1572,16 @@ function (==)(A::AbstractArray, B::AbstractArray)
15721572
if isa(A,AbstractRange) != isa(B,AbstractRange)
15731573
return false
15741574
end
1575+
anymissing = false
15751576
for (a, b) in zip(A, B)
1576-
if !(a == b)
1577+
eq = (a == b)
1578+
if ismissing(eq)
1579+
anymissing = true
1580+
elseif !eq
15771581
return false
15781582
end
15791583
end
1580-
return true
1584+
return anymissing ? missing : true
15811585
end
15821586

15831587
# sub2ind and ind2sub

base/essentials.jl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,3 +731,26 @@ This function simply returns its argument by default, since the elements
731731
of a general iterator are normally considered its "values".
732732
"""
733733
values(itr) = itr
734+
735+
"""
736+
Missing
737+
738+
A type with no fields whose singleton instance [`missing`](@ref) is used
739+
to represent missing values.
740+
"""
741+
struct Missing end
742+
743+
"""
744+
missing
745+
746+
The singleton instance of type [`Missing`](@ref) representing a missing value.
747+
"""
748+
const missing = Missing()
749+
750+
"""
751+
ismissing(x)
752+
753+
Indicate whether `x` is [`missing`](@ref).
754+
"""
755+
ismissing(::Any) = false
756+
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,
@@ -149,6 +150,7 @@ export
149150
EOFError,
150151
InvalidStateException,
151152
KeyError,
153+
MissingException,
152154
NullException,
153155
ParseError,
154156
SystemError,
@@ -881,6 +883,10 @@ export
881883
isready,
882884
fetch,
883885

886+
# missing values
887+
ismissing,
888+
missing,
889+
884890
# time
885891
sleep,
886892
time,

base/missing.jl

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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), :(one), :(oneunit),
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+
for f in (:(Base.zero), :(Base.one), :(Base.oneunit))
64+
@eval function $(f)(::Type{Union{T, Missing}}) where T
65+
T === Any && throw(MethodError($f, (Any,))) # To prevent StackOverflowError
66+
$f(T)
67+
end
68+
end
69+
70+
# Binary operators/functions
71+
for f in (:(+), :(-), :(*), :(/), :(^),
72+
:(div), :(mod), :(fld), :(rem), :(min), :(max))
73+
@eval begin
74+
# Scalar with missing
75+
($f)(::Missing, ::Missing) = missing
76+
($f)(d::Missing, x::Number) = missing
77+
($f)(d::Number, x::Missing) = missing
78+
end
79+
end
80+
81+
# Rounding and related functions
82+
for f in (:(ceil), :(floor), :(round), :(trunc))
83+
@eval begin
84+
($f)(::Missing, digits::Integer=0, base::Integer=0) = missing
85+
($f)(::Type{>:Missing}, ::Missing) = missing
86+
($f)(::Type{T}, ::Missing) where {T} =
87+
throw(MissingException("cannot convert a missing value to type $T"))
88+
end
89+
end
90+
91+
# to avoid ambiguity warnings
92+
(^)(::Missing, ::Integer) = missing
93+
94+
# Bit operators
95+
(&)(::Missing, ::Missing) = missing
96+
(&)(a::Missing, b::Bool) = ifelse(b, missing, false)
97+
(&)(b::Bool, a::Missing) = ifelse(b, missing, false)
98+
(&)(::Missing, ::Integer) = missing
99+
(&)(::Integer, ::Missing) = missing
100+
(|)(::Missing, ::Missing) = missing
101+
(|)(a::Missing, b::Bool) = ifelse(b, true, missing)
102+
(|)(b::Bool, a::Missing) = ifelse(b, true, missing)
103+
(|)(::Missing, ::Integer) = missing
104+
(|)(::Integer, ::Missing) = missing
105+
xor(::Missing, ::Missing) = missing
106+
xor(a::Missing, b::Bool) = missing
107+
xor(b::Bool, a::Missing) = missing
108+
xor(::Missing, ::Integer) = missing
109+
xor(::Integer, ::Missing) = missing
110+
111+
*(d::Missing, x::AbstractString) = missing
112+
*(d::AbstractString, x::Missing) = missing
113+
114+
function float(A::AbstractArray{Union{T, Missing}}) where {T}
115+
U = typeof(float(zero(T)))
116+
convert(AbstractArray{Union{U, Missing}}, A)
117+
end
118+
float(A::AbstractArray{Missing}) = A

base/reduce.jl

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,9 @@ Determine whether predicate `p` returns `true` for any elements of `itr`, return
597597
`true` as soon as the first item in `itr` for which `p` returns `true` is encountered
598598
(short-circuiting).
599599
600+
If the input contains [`missing`](@ref) values, return `missing` if all non-missing
601+
values are `false` (or equivalently, if the input contains no `true` value).
602+
600603
```jldoctest
601604
julia> any(i->(4<=i<=6), [3,5,7])
602605
true
@@ -610,10 +613,16 @@ true
610613
```
611614
"""
612615
function any(f, itr)
616+
anymissing = false
613617
for x in itr
614-
f(x) && return true
618+
v = f(x)
619+
if ismissing(v)
620+
anymissing = true
621+
elseif v
622+
return true
623+
end
615624
end
616-
return false
625+
return anymissing ? missing : false
617626
end
618627

619628
"""
@@ -623,6 +632,9 @@ Determine whether predicate `p` returns `true` for all elements of `itr`, return
623632
`false` as soon as the first item in `itr` for which `p` returns `false` is encountered
624633
(short-circuiting).
625634
635+
If the input contains [`missing`](@ref) values, return `missing` if all non-missing
636+
values are `true` (or equivalently, if the input contains no `false` value).
637+
626638
```jldoctest
627639
julia> all(i->(4<=i<=6), [4,5,6])
628640
true
@@ -635,12 +647,22 @@ false
635647
```
636648
"""
637649
function all(f, itr)
650+
anymissing = false
638651
for x in itr
639-
f(x) || return false
652+
v = f(x)
653+
if ismissing(v)
654+
anymissing = true
655+
# this syntax allows throwing a TypeError for non-Bool, for consistency with any
656+
elseif v
657+
continue
658+
else
659+
return false
660+
end
640661
end
641-
return true
662+
return anymissing ? missing : true
642663
end
643664

665+
644666
## in & contains
645667

646668
"""

base/reducedim.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ function reducedim_init(f, op::typeof(*), A::AbstractArray, region)
112112
end
113113
function _reducedim_init(f, op, fv, fop, A, region)
114114
T = promote_union(eltype(A))
115-
if applicable(zero, T)
115+
if T !== Any && applicable(zero, T)
116116
x = f(zero(T))
117117
z = op(fv(x), fv(x))
118118
Tr = typeof(z) == typeof(x) && !isbits(T) ? T : typeof(z)

base/sysimg.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,9 @@ const × = cross
399399
# statistics
400400
include("statistics.jl")
401401

402+
# missing values
403+
include("missing.jl")
404+
402405
# libgit2 support
403406
include("libgit2/libgit2.jl")
404407

doc/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ const PAGES = [
6767
"manual/metaprogramming.md",
6868
"manual/arrays.md",
6969
"manual/linear-algebra.md",
70+
"manual/missing.md",
7071
"manual/networking-and-streams.md",
7172
"manual/parallel-computing.md",
7273
"manual/dates.md",

doc/src/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
* [Metaprogramming](@ref)
2323
* [Multi-dimensional Arrays](@ref man-multi-dim-arrays)
2424
* [Linear Algebra](@ref)
25+
* [Missing Values](@ref missing)
2526
* [Networking and Streams](@ref)
2627
* [Parallel Computing](@ref)
2728
* [Date and DateTime](@ref)

0 commit comments

Comments
 (0)