Union{Some{T}, Nothing} behaviour

I cannot figure out how to obtain a non-nothing output from the following code.

julia> struct B
           a::Union{Some{T}, Nothing} where {T<:Integer}
       end

julia> b = B(nothing)
B(nothing)

julia> b = B(5)
ERROR: could not compute non-nothing type
Stacktrace:
 [1] error(s::String)
   @ Base .\error.jl:33
 [2] nonnothingtype_checked(T::Type)
   @ Base .\some.jl:31
 [3] convert(#unused#::Type{Union{Nothing, Some{T}} where T<:Integer}, x::Int64)
   @ Base .\some.jl:36
 [4] B(a::Int64)
   @ Main .\REPL[99]:2
 [5] top-level scope
   @ REPL[101]:1

Can you elaborate what you want to achieve?

The default constructor is trying to convert 5 to a Union{Some{T}, Nothing} where {T<:Integer}, but it doesn’t know how:

julia> convert(Union{Some{T}, Nothing} where {T <: Integer}, 5)
ERROR: could not compute non-nothing type
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] nonnothingtype_checked(::Type{T} where T) at ./some.jl:29
 [3] convert(::Type{Union{Nothing, Some{T}} where T<:Integer}, ::Int64) at ./some.jl:34
 [4] top-level scope at REPL[1]:1

You could provide a constructor for your type. Perhaps something like this:

B(x::Integer) = B(Some(x))
1 Like

I would write this as the following, which reveals why this pattern is harder to use in practice unless you make T explicit more often:

julia> struct B{T <: Integer}
           a::Union{Some{T}, Nothing}
       end

julia> B(nothing)
ERROR: UndefVarError: T not defined
Stacktrace:
 [1] B(a::Nothing)
   @ Main ./REPL[1]:2
 [2] top-level scope
   @ REPL[2]:1

julia> B(Some(1))
B{Int64}(Some(1))

julia> B{Int}(nothing)
B{Int64}(nothing)

The problem is that you can’t figure out what T is without explicitly specifying it or providing a concrete input value that lets it be determined. It’s like the problem of assigning a type to an empty array – the thing that would give you the relevant information isn’t present, so you need to make up for that absence.

1 Like

In this particular case I am trying to model a drive train. Think of a motor driving a gearbox driving a machine. Sometimes it may be more than one gearbox. Let’s call:
the motor: shaft 1
the coupling: connector 1
the gearbox input : shaft 2
the gearbox: connector 2
the gearbox output: shaft 3
the coupling: connector 4
the driven machine: shaft 4

I would like a structure that holds the information for each shaft and another that holds the information for each component.
The shaft structure has information for the previous shaft, the following shaft and the component that connects between the current shaft and the following shaft. My thought was to use nothing as the previous shaft for the motor, where there is no previous shaft. Perhaps a better way would be a separate structure for this shaft, where the absence of that field would indicate that there is no previous shaft?

In reading the documentation and on discourse, it seemed that the Union{Some{T}, Nothing} might be a good way of indicating that there is a shaft present or that nothing is present.

Thanks johnmyleswhite, that is most helpful.

For my purposes, {T::Int} may be just as good as {T<:Integer}, if this makes life easier for the compiler. Experimenting shows {T <: Int} works but {T :: Int} does not.

I see an inconsistency in passing nothing versus something to the field. So I experimented with doing away with Some and using the following which seems to provide a more consistent interface:

julia> struct B{T <: Int}
           a::Union{T, Nothing}
       end

julia> B{Int}(5)
B{Int64}(5)

julia> B{Int}(nothing)
B{Int64}(nothing)

julia> isnothing(ans.a)
true

julia>

There is no point in parametrizing T here.

struct B
    a::Union{Int, Nothing}
end

should be functionally equivalent.

Edit: Spoke to soon, B{Union{}}(nothing) is still valid with your version, but not with mine, so that might be a reason to explicitly parametrize T.

This seems to me like the best approach. Don’t be afraid to introduce new types for components that are different from each other. I try to avoid types that are filled with nothing values. If a type is filled with a bunch of nothing values, then it probably should be a different type without the nothings.

Types normally have some semantic meaning beyond just the structure of their fields. So if you have a Motor type, without looking at the fields you know that it does not have a previous shaft, because motors simply don’t have previous shafts.

I think the use of Some is overkill here. In my understanding, Some is only used in fairly specialized situations where you are trying to distinguish between the value nothing and literal nothingness. From the docstring, Some is

a wrapper type used in Union{Some{T}, Nothing} to distinguish between the absence of a value (nothing) and the presence of a nothing value (i.e. Some(nothing)).

As an example, suppose you have a dictionary of type Dict{Int, Union{Int, Nothing}}. There’s a difference between keys that don’t exist in the dictionary and keys that do exist in the dictionary but which have the value nothing.