Skip to content

The fate of Nullable #22682

Closed
Closed
@nalimilan

Description

@nalimilan

With the perspective of representing missing data as Union{T, Null} in Julia 0.7, we should decide what will happen to Nullable. I think the consensus is that being container-like, Nullable is appropriate to represent "the software engineer's null", as opposed to "the data analyst's null", a.k.a. missing values. In other words, Nullable offers three properties which Union{T, Null} does not:

  1. It does not automatically propagate missingness.
  2. It requires explicit handling of the possibility that a value may be null, even when it isn't (contrary to Union{T, Null}, where code may work when a value is of type T but not when it is of type Null, which might not have been properly anticipated/tested).
  3. It allows distinguishing Nullable{Nullable{T}} from Nullable{T} (contrary to Union{Union{T, Null}, Null} == Union{T, Null}), which is useful when you need to make the difference between "no value" and "null value". Such situations arise e.g. when doing a dictionary lookup (tryget, cf. Get dict value as a nullable #13055 and Add get(coll, key) for Associative returning Nullable #18211); when parsing a string via tryparse to a value which could either be of type T, null, or invalid; or when wrapping a value which could either be of type T or null in a Nullable before returning it from a function.

The two first features are the ones which turned out to be annoying when working with missing data, but which can provide additional safety for general programming. A detailed discussion of the advantages and drawbacks of these approaches can be found in the Nullable Julep.

Given that, several paths can be taken for Nullable in Julia 0.7:

  1. Make Nullable{T} a (deprecated) type alias for Union{T, Null}. This would have the advantage that Julia would have a single concept of null/missing values, but without the advantages of the three points above. Checks that code is correctly prepared to handle null values could still be done by a linter.
  2. Make Nullable{T} a (deprecated) type alias for Union{Some{T}, Null}, with Some{T} a wrapper around a value of type T which would behave essentially like Nullable{T} currently. Applying a function on the value would require using f.(x), broadcast or pattern matching, so that missingness would never propagate without explicitly asking for it. The advantages would be those of the three points above, at the cost of two different representations of missingness (or almost two, since the Null type would be used in both cases).
  3. Deprecate Nullable in Base and move it as-is to a package. A possible variant would be to rename it to Option in order to prevent confusion with Null and be more consistent with other languages like Scala, Rust or Swift (IIRC, Nullable was originally called Option and lived in the Options.jl package). The main advantage of this approach is to have a single representation of null values in Base, in particular to avoid setting the design of Nullable in stone in 1.0. The main issue is that no code would be able to use Nullable in Base, which implies in particular changing tryparse to return Union{T, Null}, and that no correct tryget method could be implemented for dicts (Get dict value as a nullable #13055). OTOH this could help increasing consistency with e.g. match, which returns Union{T, Void}, and uses of Nullable are not so widespread in Base.

EDIT: added mention of point 3. in the first series of bullets.

Metadata

Metadata

Labels

missing dataBase.missing and related functionalityneeds decisionA decision on this change is needed

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions