Dear Julia community,
what started as a short discourse discussion I would like to rephrase as a change request.
Currently, Base.Some
is treated like Ref
in that Some([]) != Some([])
, unlike Vector for instance. However in other programming languages, Some
actually behaves more like Vector in the sense that it is paired with Nothing and in this combination represents a Container of either one or zero elements.
For instance in Scala Some(List()) == Some(List())
.
For consistency with the general semantics of Some/Option/Optional/Maybe (or however it might be called in other programming languages), I would plead for adding the following two lines to Base
Base.:(==)(a::Some, b::Some) = a.value == b.value
Base.hash(a::Some) = hash(a.value)
This is a breaking change.
It would be great to understand your use case for this.
The motivation for Union{Nothing,Some{T}}
(compared to Union{Nothing,T}
) was being able to distinguish functions returning nothing
as the actual result (T === Nothing
). This usually means that the payload is unpacked at some point — as I suggested in the discussion, it is somewhat unusual for Some
wrappers to have an extended lifetime and be used as containers. Instead of doing
y = f(x) # obtain a value
y == Some(b) # compare
in a lot of contexts it might be more idiomatic to do
something(f(x)) == b
Doing this also allows all operations that would be valid for the payload (eg <
, etc).
The use cases which brought me here is that I ported an ExtensibleEffects framework to Julia. Within ExtensibleEffects I am working on container level, defining how containers translate to for loops, roughly speaking. In addition to for-loops, which only execute things, ExtensibleEffect construct appropriate return types.
Nothing
is a valid container in this regard, the empty container. Similarly Some
is a valid container. However if I would use Union{Nothing,T}
, I would have to decide whether T is a valid container (might be Vector for instance), or a plain value.
To solve this ambiguity the natural way is to enforce that only container types are possible for T
, hence plain values need to be given as Some{T}
instead of plain T
.
The use of the Some
here is not internal, but on the interface side of ExtensibleEffects.
ExtensibleEffects are of course only one example, anything which works on Container-Level semantics and needs to distinguish Container from Value would may use Some
for interfacing.
I would plead for adding the following two lines to Base.
You also need
Base.isequal(a::Some, b::Some) = isequal(a.value, b.value)
Base.isless(a::Some, b::Some) = isless(a.value, b.value)
and the hash
implementation needs to take a second UInt
input (and ideally be seeded by some randomly selected numbers to represent the type).
I would plead
Also - I find the most effective way of getting something small like this is to submit a PR, if you are up for it? :)
FYI, there is a PR for turning Some
to a singleton container so that it can be used for broadcasting instead of Ref
#35778.
Nice! Though that PR still doesn’t add these comparisons/hash definition, which seem reasonable with or without considering it a “container”.
I never setup Julia in development for creating pullrequest, but indeed this seems like a great occasion to go for it.
@andyferris thanks for mentioning isequal
and isless
@tkf thanks for the pointer to #35778. I read through it and also think, that the PR is separate in scope. I changed the title here respectively to avoid confusion.
okay, so it is time for me to create my first pull request :)
Most helpful comment
I never setup Julia in development for creating pullrequest, but indeed this seems like a great occasion to go for it.
@andyferris thanks for mentioning
isequal
andisless
@tkf thanks for the pointer to #35778. I read through it and also think, that the PR is separate in scope. I changed the title here respectively to avoid confusion.
okay, so it is time for me to create my first pull request :)