diff --git a/NEWS.md b/NEWS.md index 9e599898d452a..f67973a72a1be 100644 --- a/NEWS.md +++ b/NEWS.md @@ -46,6 +46,7 @@ New library functions * Two arguments method `lock(f, lck)` now accepts a `Channel` as the second argument. ([#39312]) * New functor `Returns(value)`, which returns `value` for any arguments ([#39794]) * New macro `Base.@invoke f(arg1::T1, arg2::T2; kwargs...)` provides an easier syntax to call `invoke(f, Tuple{T1,T2}, arg1, arg2; kwargs...)` ([#38438]) +* New macros `@something` and `@coalesce` which are short-circuiting versions of `something` and `coalesce`, respectively ([#40729]) New library features -------------------- diff --git a/base/exports.jl b/base/exports.jl index bfca26745e2ac..125e38930640d 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -699,9 +699,11 @@ export # missing values coalesce, + @coalesce, ismissing, missing, skipmissing, + @something, something, isnothing, nonmissingtype, diff --git a/base/missing.jl b/base/missing.jl index 87a63eb6327dc..1c15786a86a51 100644 --- a/base/missing.jl +++ b/base/missing.jl @@ -401,12 +401,12 @@ function filter(f, itr::SkipMissing{<:AbstractArray}) end """ - coalesce(x, y...) + coalesce(x...) Return the first value in the arguments which is not equal to [`missing`](@ref), if any. Otherwise return `missing`. -See also [`skipmissing`](@ref), [`something`](@ref). +See also [`skipmissing`](@ref), [`something`](@ref), [`@coalesce`](@ref). # Examples @@ -428,3 +428,36 @@ function coalesce end coalesce() = missing coalesce(x::Missing, y...) = coalesce(y...) coalesce(x::Any, y...) = x + + +""" + @coalesce(x...) + +Short-circuiting version of [`coalesce`](@ref). + +# Examples +```jldoctest +julia> f(x) = (println("f(\$x)"); missing); + +julia> a = 1; + +julia> a = @coalesce a f(2) f(3) error("`a` is still missing") +1 + +julia> b = missing; + +julia> b = @coalesce b f(2) f(3) error("`b` is still missing") +f(2) +f(3) +ERROR: `b` is still missing +[...] +``` +""" +macro coalesce(args...) + expr = :(missing) + for arg in reverse(args) + expr = :((val = $arg) !== missing ? val : $expr) + end + return esc(:(let val; $expr; end)) +end + diff --git a/base/some.jl b/base/some.jl index 30fc36d32e13f..f762f7f9aa4d1 100644 --- a/base/some.jl +++ b/base/some.jl @@ -71,13 +71,13 @@ isnothing(x) = x === nothing """ - something(x, y...) + something(x...) Return the first value in the arguments which is not equal to [`nothing`](@ref), if any. Otherwise throw an error. Arguments of type [`Some`](@ref) are unwrapped. -See also [`coalesce`](@ref), [`skipmissing`](@ref). +See also [`coalesce`](@ref), [`skipmissing`](@ref), [`@something`](@ref). # Examples ```jldoctest @@ -100,3 +100,43 @@ something() = throw(ArgumentError("No value arguments present")) something(x::Nothing, y...) = something(y...) something(x::Some, y...) = x.value something(x::Any, y...) = x + + +""" + @something(x...) + +Short-circuiting version of [`something`](@ref). + +# Examples +```jldoctest +julia> f(x) = (println("f(\$x)"); nothing); + +julia> a = 1; + +julia> a = @something a f(2) f(3) error("Unable to find default for `a`") +1 + +julia> b = nothing; + +julia> b = @something b f(2) f(3) error("Unable to find default for `b`") +f(2) +f(3) +ERROR: Unable to find default for `b` +[...] + +julia> b = @something b f(2) f(3) Some(nothing) +f(2) +f(3) + +julia> b === nothing +true +``` +""" +macro something(args...) + expr = :(nothing) + for arg in reverse(args) + expr = :((val = $arg) !== nothing ? val : $expr) + end + return esc(:(something(let val; $expr; end))) +end + diff --git a/doc/src/base/base.md b/doc/src/base/base.md index d371f7aad9411..7d1160dac918b 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -229,6 +229,7 @@ Base.isnothing Base.notnothing Base.Some Base.something +Base.@something Base.Enums.Enum Base.Enums.@enum Core.Expr @@ -287,6 +288,7 @@ Base.@deprecate Base.Missing Base.missing Base.coalesce +Base.@coalesce Base.ismissing Base.skipmissing Base.nonmissingtype diff --git a/test/missing.jl b/test/missing.jl index 0610377a7e67a..e1042f76fc7a7 100644 --- a/test/missing.jl +++ b/test/missing.jl @@ -575,6 +575,16 @@ end @test coalesce(missing, nothing) === nothing end +@testset "@coalesce" begin + @test @coalesce() === missing + @test @coalesce(1) === 1 + @test @coalesce(nothing) === nothing + @test @coalesce(missing) === missing + + @test @coalesce(1, error("failed")) === 1 + @test_throws ErrorException @coalesce(missing, error("failed")) +end + mutable struct Obj; x; end @testset "weak references" begin @noinline function mk_wr(r, wr) diff --git a/test/some.jl b/test/some.jl index 224eb8600814c..b2111c8b86085 100644 --- a/test/some.jl +++ b/test/some.jl @@ -79,6 +79,16 @@ @test something(missing, nothing, missing) === missing end +@testset "@something" begin + @test_throws ArgumentError @something() + @test_throws ArgumentError @something(nothing) + @test @something(1) === 1 + @test @something(Some(nothing)) === nothing + + @test @something(1, error("failed")) === 1 + @test_throws ErrorException @something(nothing, error("failed")) +end + # issue #26927 a = [missing, nothing, Some(nothing), Some(missing)] @test a isa Vector{Union{Missing, Nothing, Some}}