Propagating Nothing when a parameter is Nothing generically

I want something like this for a collection of a couple dozen small functions.

f(a::Nothing, b) = nothing
f(a, b::Nothing) = nothing
f(a::Nothing, b::Nothing) = nothing

f(a, b) = a + b

But I wish to do so without adding that code to each of the few dozen actual implementations (eg: a + b) and without adding 3 additional methods.

I believe if you write it like:

function f(a, b)
    (isnothing(a) || isnothing(b)) && return nothing
    ... do your normal thing...
end

the compiler will optimize it to equivalently what you wrote up there

EDIT: thanks to @Per for pointing out || && priority

4 Likes

This is a potentially good use case for a macro. You can end up with something like:

@propogate_nothing f(a, b) = a + b

And have it translate into:

f(a::Nothing, b) = nothing
f(a, b::Nothing) = nothing
f(a::Nothing, b::Nothing) = nothing
f(a, b) = a + b
2 Likes

Yes, it will. Use cases like this is one time it is good idea to do type checking inside the body over dispatch.

3 Likes

You will need parentheses:

(isnothing(a) || isnothing(b)) && return nothing

since || has lower priority than &&.

1 Like

You may be better off working with missing, instead of nothing. Unlike nothing, missing is designed to propagate, and has ready-made utilities to make propagation easier.

See passmissing from Missings.jl

help?> passmissing
search: passmissing

  passmissing(f)


  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.
5 Likes

One of the conceptual distinctions between nothing and missing (imo) is that nothing is handled immediately (sometimes by converting to missing) or an error is thrown.
Where as missing is propagated until it is handled (e.g. with imputation) or until it can’t be supported and then an error is thrown

2 Likes