From 151658017cbe8b409797b5eeeec5fd56686d40f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogumi=C5=82=20Kami=C5=84ski?= Date: Thu, 30 Aug 2018 10:15:22 +0200 Subject: [PATCH 1/7] add conditional and passmissing --- src/Missings.jl | 45 ++++++++++++++++++++++++++++++++++++++++++++- test/runtests.jl | 6 ++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/Missings.jl b/src/Missings.jl index 84d6dee..4daed20 100644 --- a/src/Missings.jl +++ b/src/Missings.jl @@ -1,7 +1,8 @@ module Missings export allowmissing, disallowmissing, ismissing, missing, missings, - Missing, MissingException, levels, coalesce + Missing, MissingException, levels, coalesce, + conditional, passmissing using Base: ismissing, missing, Missing, MissingException @@ -165,4 +166,46 @@ function levels(x) levs end +struct Conditional{P,X,Y} <: Function end + +""" + conditional(predicate, x, y) + +Return a function that that applies function `predicate` to its positional and keyword +arguments and returns the value of `x` applied to those arguments if `predicate` +returns true and otherwise returns the value of `y` applied to those arguments. + +# Examples +```jldoctest +julia> f = conditional(x -> x ≥ 0, sqrt, x -> sqrt(complex(x))); + +julia> f.([4, -4]) +2-element Array{Number,1}: + 2.0 + 0.0 + 2.0im +""" +conditional(predicate::Function, x::Base.Callable, y::Base.Callable) = + Conditional{predicate, x, y}() +(::Conditional{P,X,Y})(xs...;kw...) where {P,X,Y} = + P(xs...; kw...) ? X(xs...; kw...) : Y(xs...; kw...) + +_passmissing_predicate(xs...;kw...) = + any(ismissing.(xs)) || any(ismissing.(values(values(kw)))) +_passmissing_value(xs...;kw...) = missing + +""" + passmissing(f) + +Return a function that returns `missing` if any of its positional or keyword arguments +are `missing` and otherwise applies `f` to those arguments. + +# Examples +```jldoctest +julia> passmissing(sqrt).([missing, 4]) +2-element Array{Union{Missing, Float64},1}: + missing + 2.0 +""" +passmissing(f) = conditional(_passmissing_predicate, _passmissing_value, f) + end # module diff --git a/test/runtests.jl b/test/runtests.jl index c95aa45..a1caae8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -355,4 +355,10 @@ using Test, Dates, InteractiveUtils, SparseArrays, Missings # MissingException @test sprint(showerror, MissingException("test")) == "MissingException: test" + + # Lifting + fun1 = conditional(x -> x ≥ 0, sqrt, x -> sqrt(complex(x))); + @test fun1(4) === 2.0 + @test fun1(-4) === 2.0im + @test isequal(passmissing(sqrt).([missing, 4]), [missing, 2.0]) end From 3bf10c00b31a46f44d89c724a543cbee9be77ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogumi=C5=82=20Kami=C5=84ski?= Date: Fri, 31 Aug 2018 21:10:49 +0200 Subject: [PATCH 2/7] leave a minimal implementation of passmissing --- src/Missings.jl | 33 +++++---------------------------- test/runtests.jl | 4 +--- 2 files changed, 6 insertions(+), 31 deletions(-) diff --git a/src/Missings.jl b/src/Missings.jl index 4daed20..8c56fb3 100644 --- a/src/Missings.jl +++ b/src/Missings.jl @@ -1,8 +1,7 @@ module Missings export allowmissing, disallowmissing, ismissing, missing, missings, - Missing, MissingException, levels, coalesce, - conditional, passmissing + Missing, MissingException, levels, coalesce, passmissing using Base: ismissing, missing, Missing, MissingException @@ -166,32 +165,10 @@ function levels(x) levs end -struct Conditional{P,X,Y} <: Function end +struct PassMissing{F} <: Function end -""" - conditional(predicate, x, y) - -Return a function that that applies function `predicate` to its positional and keyword -arguments and returns the value of `x` applied to those arguments if `predicate` -returns true and otherwise returns the value of `y` applied to those arguments. - -# Examples -```jldoctest -julia> f = conditional(x -> x ≥ 0, sqrt, x -> sqrt(complex(x))); - -julia> f.([4, -4]) -2-element Array{Number,1}: - 2.0 - 0.0 + 2.0im -""" -conditional(predicate::Function, x::Base.Callable, y::Base.Callable) = - Conditional{predicate, x, y}() -(::Conditional{P,X,Y})(xs...;kw...) where {P,X,Y} = - P(xs...; kw...) ? X(xs...; kw...) : Y(xs...; kw...) - -_passmissing_predicate(xs...;kw...) = - any(ismissing.(xs)) || any(ismissing.(values(values(kw)))) -_passmissing_value(xs...;kw...) = missing +(::PassMissing{F})(xs...;kw...) where {F} = + any(ismissing, xs) || any(ismissing, values(values(kw))) ? missing : F(xs...; kw...) """ passmissing(f) @@ -206,6 +183,6 @@ julia> passmissing(sqrt).([missing, 4]) missing 2.0 """ -passmissing(f) = conditional(_passmissing_predicate, _passmissing_value, f) +passmissing(f::Base.Callable) = PassMissing{f}() end # module diff --git a/test/runtests.jl b/test/runtests.jl index a1caae8..06ac926 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -357,8 +357,6 @@ using Test, Dates, InteractiveUtils, SparseArrays, Missings @test sprint(showerror, MissingException("test")) == "MissingException: test" # Lifting - fun1 = conditional(x -> x ≥ 0, sqrt, x -> sqrt(complex(x))); - @test fun1(4) === 2.0 - @test fun1(-4) === 2.0im @test isequal(passmissing(sqrt).([missing, 4]), [missing, 2.0]) + @test isequal(passmissing(parse)(Int, "a", base=missing), missing) end From f947bcf65aa0ea7b8a8d0abc0f29feae420a8770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogumi=C5=82=20Kami=C5=84ski?= Date: Sat, 8 Sep 2018 12:48:41 +0200 Subject: [PATCH 3/7] Improved docstring --- src/Missings.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Missings.jl b/src/Missings.jl index 8c56fb3..14acef0 100644 --- a/src/Missings.jl +++ b/src/Missings.jl @@ -178,6 +178,12 @@ are `missing` and otherwise applies `f` to those arguments. # Examples ```jldoctest +julia> passmissing(sqrt)(4) +2.0 + +julia> passmissing(sqrt)(missing) +missing + julia> passmissing(sqrt).([missing, 4]) 2-element Array{Union{Missing, Float64},1}: missing From e775f2bda3e2bb8d5f485dd53d49f46e92f0728c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogumi=C5=82=20Kami=C5=84ski?= Date: Sun, 9 Sep 2018 01:28:55 +0200 Subject: [PATCH 4/7] use generated function --- src/Missings.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Missings.jl b/src/Missings.jl index 14acef0..5b0153d 100644 --- a/src/Missings.jl +++ b/src/Missings.jl @@ -167,8 +167,8 @@ end struct PassMissing{F} <: Function end -(::PassMissing{F})(xs...;kw...) where {F} = - any(ismissing, xs) || any(ismissing, values(values(kw))) ? missing : F(xs...; kw...) +@generated (::PassMissing{F})(xs...;kw...) where {F} = + :(any(ismissing, xs) || any(ismissing, values(values(kw))) ? missing : F(xs...; kw...)) """ passmissing(f) From 4cc1a5ee3698ad40e934204893943b57ec293c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogumi=C5=82=20Kami=C5=84ski?= Date: Tue, 8 Jan 2019 16:21:48 +0100 Subject: [PATCH 5/7] improved passmissing implementation --- src/Missings.jl | 37 +++++++++++++++++++++++++++++++------ test/runtests.jl | 5 ++++- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/Missings.jl b/src/Missings.jl index 5b0153d..cf9f87e 100644 --- a/src/Missings.jl +++ b/src/Missings.jl @@ -165,16 +165,35 @@ function levels(x) levs end -struct PassMissing{F} <: Function end +struct PassMissing{F} <: Function + f::F +end -@generated (::PassMissing{F})(xs...;kw...) where {F} = - :(any(ismissing, xs) || any(ismissing, values(values(kw))) ? missing : F(xs...; kw...)) +function (f::PassMissing{F})(x) where {F} + if @generated + return x === Missing ? missing : :(f.f(x)) + else + return x === missing ? missing : f.f(x) + end +end + +function (f::PassMissing{F})(xs...) where {F} + if @generated + for T in xs + T === Missing && return missing + end + return :(f.f(xs...)) + else + return any(ismissing, xs) ? missing : f.f(xs...) + end +end """ passmissing(f) -Return a function that returns `missing` if any of its positional or keyword arguments -are `missing` and otherwise applies `f` to those arguments. +Return a function that returns `missing` if any of its positional arguments +are `missing` (even if their number or type is not consistent with any of the +methods defined for `f`) and otherwise applies `f` to these arguments. # Examples ```jldoctest @@ -188,7 +207,13 @@ julia> passmissing(sqrt).([missing, 4]) 2-element Array{Union{Missing, Float64},1}: missing 2.0 + +julia> passmissing((x,y)->"$x $y")(1, 2) +"1 2" + +julia> passmissing((x,y)->"$x $y")(missing) +missing """ -passmissing(f::Base.Callable) = PassMissing{f}() +passmissing(f::Base.Callable) = PassMissing{typeof(f)}(f) end # module diff --git a/test/runtests.jl b/test/runtests.jl index 06ac926..bcabcf4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -357,6 +357,9 @@ using Test, Dates, InteractiveUtils, SparseArrays, Missings @test sprint(showerror, MissingException("test")) == "MissingException: test" # Lifting + @test passmissing(sqrt)(4) == 2.0 + @test isequal(passmissing(sqrt)(missing), missing) @test isequal(passmissing(sqrt).([missing, 4]), [missing, 2.0]) - @test isequal(passmissing(parse)(Int, "a", base=missing), missing) + @test passmissing((x,y)->"$x $y")(1, 2) == "1 2" + @test isequal(passmissing((x,y)->"$x $y")(missing), missing) end From 7f491bff168ad2bbf28e62a7674ab4d362496a53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogumi=C5=82=20Kami=C5=84ski?= Date: Tue, 8 Jan 2019 16:27:47 +0100 Subject: [PATCH 6/7] fix a typo --- src/Missings.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Missings.jl b/src/Missings.jl index cf9f87e..35fb750 100644 --- a/src/Missings.jl +++ b/src/Missings.jl @@ -208,10 +208,10 @@ julia> passmissing(sqrt).([missing, 4]) missing 2.0 -julia> passmissing((x,y)->"$x $y")(1, 2) +julia> passmissing((x,y)->"\$x \$y")(1, 2) "1 2" -julia> passmissing((x,y)->"$x $y")(missing) +julia> passmissing((x,y)->"\$x \$y")(missing) missing """ passmissing(f::Base.Callable) = PassMissing{typeof(f)}(f) From 8c0bd5de5f1e06c588758a3674a0d8b409d58d60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogumi=C5=82=20Kami=C5=84ski?= Date: Wed, 9 Jan 2019 20:02:32 +0100 Subject: [PATCH 7/7] test keyword arguments --- src/Missings.jl | 2 ++ test/runtests.jl | 1 + 2 files changed, 3 insertions(+) diff --git a/src/Missings.jl b/src/Missings.jl index 35fb750..63a8efc 100644 --- a/src/Missings.jl +++ b/src/Missings.jl @@ -195,6 +195,8 @@ Return a function that returns `missing` if any of its positional arguments are `missing` (even if their number or type is not consistent with any of the methods defined for `f`) and otherwise applies `f` to these arguments. +`passmissing` does not support passing keyword arguments to the `f` function. + # Examples ```jldoctest julia> passmissing(sqrt)(4) diff --git a/test/runtests.jl b/test/runtests.jl index bcabcf4..576c67c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -362,4 +362,5 @@ using Test, Dates, InteractiveUtils, SparseArrays, Missings @test isequal(passmissing(sqrt).([missing, 4]), [missing, 2.0]) @test passmissing((x,y)->"$x $y")(1, 2) == "1 2" @test isequal(passmissing((x,y)->"$x $y")(missing), missing) + @test_throws ErrorException passmissing(string)(missing, base=2) end