Optional and orElse: design feedback/critique?

aliak something at something.com
Sat Jul 27 13:17:32 UTC 2019


Hi,

Can I ask for feedback on what people expect an optional/maybe 
type to have, and how to behave. There have been a number of 
discussions on the forums about it so far, and attempted PRs in 
phobos. And I currently maintain one that I've been using 
in-house very happily (backend server with vibe-d). I want to 
nail it down further and get to a version 1.0.0 so feedback and 
comments would be highly appreciated.

Optional semantics:
===================

So first, the semantics of Optional!T (these may or may not 
already be implemented):
* Is at least a forward range
* Equality with itself, T, and sentinel-type "none", which checks 
if optional is empty or not
* Assignment to T, `none`, or Optional!T
* Includes type constructors some(x) and no!T
* Forwards operators to T if T implements those operators
   E.g. some(1)++ and no!int++ both work
   E.g. If T has an opCall(...), then some(x)(...) and no!T()(...) 
both work
* Null pointers are valid objects, so some!(int*)(null) is non 
empty
* Null classses and interfaces are empty, so some!Class(null) is 
empty

Optional utilities:
===================

Two utilities are included. Optional chaining and a match 
function. Optional chaining allows you to go through an object's 
hierarchy and get an optional back at the end:

oc(obj).property.function()

If obj is none, then the end result is no!T where T is the return 
type of function. Else it has the actual value if the chain goes 
all the way though. This will also work if property is in turn an 
Optional itself. Secondly, the chaining function is also provided 
for NullableT and reference type.

The match function takes two handlers (lambda aliases) and calls 
the correct one based on if the Optional!T is empty or not.

orElse semantics:
=================

orElse will either get the front of a range or the range itself 
depending on the alternative value and also works on reference 
types. So:

Range!int range1, range2;
range1.orElse(range2) // returns range1 if range1 is not empty, 
else range2
range1.orElse(8) // returns front or range1 if non empty, else 8.

In essence, it also coalesces. I was on the fence on this, but 
it's turning out (again in our project) to be very convenient. 
I'm considering doing this for primitive types like int, float, 
or anything that can be cast to bool (while taking things like 
NaN in to account for e.g.)

So orElse works on a number of types and these are each types's 
semantics:

* If T is a reference type, val1.orElse(val2) will return val1 if 
(val1 !is null), else val2
* If T is a range, the example above shows the semantics
* If T is an Optional!U then it is treated separately in order to 
work with const Optional!U. The value returned by orElse is 
determined by the alternative value. If it is a U then 
optional.front will be returned, if it is an Optional!U then an 
optional will be returned. So the same semantics of range.
* If T is Nullable!U, then isNull is checked. If it is false and 
alternative value is a U, then nullable.get is returned. If T is 
another Nullable, then the return type is a Nullable.

I may have forgotten some stuff but I think that's everything.

Thanks in advance!

PS: I realize an obvious first comment is "what about 
Nullable!T". A number of reasons: 1) it doesn't have a range 
interface 2) the reference semantics are not desirable for me and 
writing generic code is awkward 
(https://github.com/aliak00/optional#what-about-stdtypeconsnullable), 3) alias this, although deprecated, breaks it completely, 4) maintaining a dub package feels much more productive.


More information about the Digitalmars-d mailing list