From 93f775fe4cfd0d1cf79d424a2ab094874b13fd01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogumi=C5=82=20Kami=C5=84ski?= Date: Fri, 30 Mar 2018 12:54:52 +0200 Subject: [PATCH] add lift function for working with missing values --- base/exports.jl | 1 + base/missing.jl | 44 +++++++++++++++++++++++++++++++++++++ doc/src/base/base.md | 1 + test/missing.jl | 52 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+) diff --git a/base/exports.jl b/base/exports.jl index 15de30529884f..32a91123f7e68 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -705,6 +705,7 @@ export ismissing, missing, skipmissing, + lift, # time sleep, diff --git a/base/missing.jl b/base/missing.jl index 6fb7556aa9af4..fd6a8ff2b2609 100644 --- a/base/missing.jl +++ b/base/missing.jl @@ -218,3 +218,47 @@ end @inbounds v = itr.x[i]::eltype(itr) (v, _next_nonmissing_ind(itr.x, state)) end + +""" + lift(f) + lift(f, x...; kw...) + +Lift function `f` so that it returns `missing` when any of its positional arguments +is `missing`. Otherwise `f` is applied to its arguments. + +The form `lift(f)` returns an anonymous function that has lifted behavior. +The form `lift(f, x...; kw...)` returns `missing` if any of `x` is `missing` +and otherwise returns `f(x...; kw...)`. + +# Examples +```jldoctest +julia> g = lift(uppercase); + +julia> g("a") +"A" + +julia> g(missing) +missing + +julia> lift(parse, Int, "aa", base=16) +170 + +julia> lift(parse, missing, "aa", base=16) +missing + +julia> lift(parse).(Int, ["1", "2", missing]) +3-element Array{Union{Missing, Int64},1}: + 1 + 2 + missing + +julia> lift.(parse, Int, ["a", "b", missing], base=16) +3-element Array{Union{Missing, Int64},1}: + 10 + 11 + missing +``` +""" +lift(f::Function) = (x...; kw...) -> any(ismissing, x) ? missing : f(x...; kw...) +lift(f::Function, x; kw...) = ismissing(x) ? missing : f(x; kw...) +lift(f::Function, x...; kw...) = any(ismissing, x) ? missing : f(x...; kw...) diff --git a/doc/src/base/base.md b/doc/src/base/base.md index f9a8354b90229..e28190d2e478b 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -222,6 +222,7 @@ Base.missing Base.coalesce Base.ismissing Base.skipmissing +Base.lift ``` ## System diff --git a/test/missing.jl b/test/missing.jl index 6825c07bbe74b..cebd9fd6fef98 100644 --- a/test/missing.jl +++ b/test/missing.jl @@ -362,3 +362,55 @@ end @test collect(x) == [1, 2, 4] @test collect(x) isa Vector{Int} end + +@testset "lift" begin + x = ["a", missing, "b", missing] + for v in x + if ismissing(v) + @test ismissing(lift(uppercase)(v)) + @test ismissing(lift(uppercase, v)) + else + @test lift(uppercase)(v) == uppercase(v) + @test lift(uppercase, v) == uppercase(v) + end + end + ref = [ismissing(v) ? missing : uppercase(v) for v in x] + @test isequal(lift(uppercase).(x), ref) + @test isequal(lift.(uppercase, x), ref) + + x = ["12345", missing, "1234567", missing] + for v in x + if ismissing(v) + @test ismissing(lift(chop)(v, head=2, tail=2)) + @test ismissing(lift(chop, v, head=2, tail=2)) + else + @test lift(chop)(v, head=2, tail=2) == chop(v, head=2, tail=2) + @test lift(chop, v, head=2, tail=2) == chop(v, head=2, tail=2) + end + end + ref = [ismissing(v) ? missing : chop(v, head=2, tail=2) for v in x] + @test isequal(lift(chop).(x, head=2, tail=2), ref) + @test isequal(lift.(chop, x, head=2, tail=2), ref) + + s = ["12345", missing, "1234567", missing] + t = [Int, Int, missing, missing] + for (v, w) in zip(s, t) + if ismissing(v) || ismissing(w) + @test ismissing(lift(parse)(w, v)) + @test ismissing(lift(parse, w, v)) + @test ismissing(lift(parse)(w, v, base=16)) + @test ismissing(lift(parse, w, v, base=16)) + else + @test lift(parse)(w, v) == parse(w, v) + @test lift(parse, w, v) == parse(w, v) + @test lift(parse)(w, v, base=16) == parse(w, v, base=16) + @test lift(parse, w, v, base=16) == parse(w, v, base=16) + end + end + ref10 = [any(ismissing, (v,w)) ? missing : parse(w, v) for (v, w) in zip(s, t)] + ref16 = [any(ismissing, (v,w)) ? missing : parse(w, v, base=16) for (v, w) in zip(s, t)] + @test isequal(lift(parse).(t, s), ref10) + @test isequal(lift(parse).(t, s, base=16), ref16) + @test isequal(lift.(parse, t, s), ref10) + @test isequal(lift.(parse, t, s, base=16), ref16) +end