Coming back to this thread, I think we've ended up stuck with the same incompatible design goals again.
As I see it, progagation can work in a couple of ways:
(1) Always off: you can't propagate nullability without explicitly constructing a Nullable object.
(2) Opt-in at the call-site: you can propagate nullability by using something like map(f, Nullable).
(3) Opt-in near the call-site: you can propagate nullability within a block of code using something like @propagate begin x = Nullable(1) + Nullable(2); sin(x) end
(4) Opt-in at the definition site: you can propagate nullability by annotating a function as propagating in the same way that we annotate functions as vectorizable.
(5) Always on: you can't prevent propagation without explicitly checking for nullability before calling any function.
There are strengths and weaknesses to all of these approaches. We currently have (1), which is the most verbose, but also the safest.
There was a proposal to move to (2), which would make things less verbose and comparably safe, but would still be more verbose than R. The verbose is very severe in complex expressions like sin(cos(x)) + cos(sin(x - pi)).
There was a proposal to move to (3), which requires specifying the semantics of @propagate in greater detail, but makes things much less verbose. It does so at the cost of some safety since you might wrap too large a block with @propagate. But it's always explicit, which is a virtue.
There was a proposal to move to (4), which requires a lot of on-going labor to ensure that all relevant functions are annotated properly. There's also an issue in which the annotations that one person believes are reasonable are different rom the annotations preferred by others. Should, for example, length(Nullable{Array}) work by default?
Finally, there was a proposal to move to (5), which requires no effort, but allows strange things like push!(Nullable{Int}()) to execute without errors.
There are also technical impediments to be dealt with:
(1) No problems here. We have this working already.
(2) Higher-order functions are slow. Until functions have a richer place in the type system, map will likely continue to be a performance trap.
(3) The semantics of propagate are pretty vague. If the code expands to uses of map, we have the performance problems noted in (2).
(4) We've tried doing annotations before in DataArrays. It requires a huge amount of work -- more than anyone can reasonably expect to do. Given my very negative experiences doing this work for DataArrays, I'm pretty strongly opposed to this proposal.
(5) Call overloading currently doesn't work this way. It could be extended, but that changes Base in a deep way just to provide this feature.
Given that we need a clear plan, I'm going to make a judgment call here and rescind my previous plan for trying to implement (5) one day. Instead, let's push for a version of (3) that does not involve calling map. We can also implement map, with the understanding that map cannot be used in high-performance code until the type system gets another iteration.
-- John