Union{Nothing, T} vs Union{Nothing, Some{T}) -- I don't understand the utility of Some(nothing)

I’m trying to figure out what to do with my code that uses Nullable. After looking at the documentation, some posts on here, and this blog post I don’t really understand when to use Union{Nothing, Some{T}} over Union{Nothing, T}. The blog post says:

As a special case, if nothing is a possible value (i.e. Nothing <: T ), Union{Nothing,Some{T}} should be used instead

which still doesn’t make sense to me. When would it be useful to distinguish nothing from Some(nothing)?

It’s a matter of how your code interprets the data. For example, nothing could mean, “this attribute is not stored for this object” while Some(nothing) could mean, "this attribute is stored, and its value is nothing". Without the Some() wrapper, you can’t distinguish those two cases.

Or another way of putting it: you might ask me what kind of car I own. I might not respond (return nothing) or I might respond that I do not own a car (return Some(nothing)).

9 Likes

Love it, very clear, thanks!

Another example would be a dictionary object, where you look up a particular key. If the key is not found you might return nothing, but the key might exist with a value of nothing. So to distinguish those two cases you might return Some(v) for the value of any key (it could be Some(nothing), and nothing when the key isn’t found.

2 Likes

How does this relate to the nothing vs. missing distinction?

I think missing is designed to silently propagate, as a data value that exists and is unknown, while nothing is designed to be the absence of a data value, and not silently propagate. I think the intent is for missing to be accepted by most functions that take normal values, while nothing is not.

Also nothing and missing behave quite differently in comparisons, e.g.

julia> 1 == nothing
false

julia> 1 == missing
missing

julia> nothing == nothing
true

julia> missing == missing
missing

julia> 1 + missing
missing

julia> 1 + nothing
ERROR: MethodError: no method matching +(::Int64, ::Nothing)
Closest candidates are:
  +(::Any, ::Any, ::Any, ::Any...) at operators.jl:502
  +(::T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8}, ::T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8}) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} at int.jl:53
  +(::Union{Int16, Int32, Int64, Int8}, ::BigInt) at gmp.jl:456
  ...
Stacktrace:
 [1] top-level scope at none:0

Thanks for the detailed reply. I’m still confused as to why one would use Some(nothing) instead of missing (if that even makes sense…)

In particular, I’m trying to figure out to what extent the similarities in the following two quotes indeed correspond to equivalent concepts:

For example, nothing could mean, “this attribute is not stored for this object” while Some(nothing) could mean, “this attribute is stored, and its value is nothing”.

I think missing is designed to [be] a data value that exists and is unknown, while nothing is designed to be the absence of a data value

Reading the two, it seems to me that missing is intended to play a similar role than Some(nothing), so when should one use one or the other?

missing means “there is a valid value, but we don’t know what it is”. This is most often used in a data analysis context.

In the examples above Some(nothing) is used to say "there is a value here, we know what it is, and the value is nothing. In this case nothing has some meaning that depends on the context. For instance, say you have a function that wraps another function and returns its return value on success, but on failure returns nothing. The problem is that the wrapped function might itself return nothing, so you wrap the return value in Some(...). So in this case Some(nothing) would mean that the wrapped function returned nothing, and nothing means that an error occurred.

(note this specific example is a bad idea in most cases, because you lose all information about the error that occured)

code example:

function catcherr(f)
    try
        Some(f())
    catch
        nothing
    end
end

foo() = 42
bar() = nothing
baz() = raise(ErrorException("badness"))

julia> catcherr(foo)
Some(42)

julia> catcherr(bar)
Some(nothing)

julia> catcherr(baz) # returns `nothing`

julia>
1 Like