Avoid ugly 'isempty' pattern

Hello!

Through some Discourse posts and my own personal experience, I’ve seen a pattern for isempty that repeats a lot, but there is no easy way around it. It is the case for when the result of a function call returns an empty array (it may happen with nothing also), and a “default” return is desired instead in that case.

Here is a MWE:

# 'f' is a computationally expensive function, so it should not be called twice.
# f(x)::Vector{Int64}
default_ = [-1, 1, 0]

# Call to function
a = f(t)

# Return a or 'default' if a is empty
a = isempty(a) ? default_ : a

I cannot use map because it does not work for empty collections. Is there an easier way to do this?
Or at least, a way to do it idiomatically (in one line/function call).

Thanks!

For the case of nothing, there is

julia> x=nothing; something(x, 5)
5
3 Likes

I didn’t know about something, that’s great!!

Unfortunately, it is still missing the isempty case :slightly_frowning_face:

You could define your own helper function for the isempty case, analogue to something.

my_something(x,y) = (x === nothing || isempty(x)) ? y : x

Note that it is not best practice in Julia to have a function return different types depending on run-time information (different output types for different input types is fine) because this is not type-stable.
Thus, better write your function f such that it always returns the same type, i.e. an array which might be empty.

It’s not quite what you’re asking for, but Maybe.jl addresses a similar concern. More often than not, the reason you care about emptiness of an array is because you want to use functions like first which assume a nonempty array, and Maybe helps with that problem.

On a different note, this topic should be in the Usage discourse category rather than Internals.

1 Like

I’m not sure how useful is this, but this is an alternative:

julia> Base.@kwdef mutable struct F
           called::Bool = false
           default::Vector{Int} = [-1,1,0]
       end
F

julia> function (f::F)(t)
           f.called && return f.default
           f.called = true
           # expensive stuff
           return t
       end

julia> f = F()
F(false, [-1, 1, 0])

julia> a = f(1.0)
1.0

julia> a = f(1.0)
3-element Vector{Int64}:
 -1
  1
  0

Is there a reason f can’t be modified to return the default value rather than an empty vector?

1 Like

Sometimes the function is not owned by the caller :frowning:

This is close to what I meant, yeah. Thanks!!

On a side note, I put it in Internals because I thought it could spark a conversation about it and maybe push it further down to Base, but yeah, I agree that General Usage is better.