Optional and orElse: design feedback/critique?

Paul Backus snarwin at gmail.com
Sun Jul 28 00:05:19 UTC 2019


On Saturday, 27 July 2019 at 16:39:21 UTC, aliak wrote:
> Egad! I just (i.e. today/yesterday) removed NotNull as I could 
> not figure out how to guarantee NotNull semantics without it 
> just being awkward to use. I'm not sure it's generally used 
> either. But that's based on what I've been using myself, and 
> issues and prs in the repo - so not a very wide view.
>
> But maybe you're right in making it the same. I'm a bit weary 
> because null classes inside optional values has bitten me and 
> colleagues multiple times in Swift, Scala, and Kotlin over the 
> past few years. Swift got rid of the issue in version 2 or 
> something. Scala and Kotlin still have it because of necessary 
> Java interop.

As suggested elsewhere in this thread, treating both null 
references and null pointers as empty would also be a reasonable 
decision. The inconsistency is the real issue here.

> The thing is I want it to "just work" based on the call site. 
> So if a user does a.orElse(literal) on a container type, it's 
> pretty obvious what they want. If a user does 
> a.orElse(container), it's again (I think) obvious what they 
> want. But essentially those are the only two cases.  And since 
> reference types are not containers, then only the second case 
> applies. Does that make it simpler?

To me, it's obvious that the expression `a.orElse(b)` should 
return either a or b. If I ask the computer to choose between two 
alternatives for me, and instead it gives me a third thing that I 
didn't ask for (e.g., `a.front`), I am going to be very confused.

For Optional, there's a one-to-one correspondence between the 
container and the value inside it, so eliding the distinction 
isn't a big deal (though strictly speaking, `valueOrElse` would 
be a more accurate name). On the other hand, I'd definitely be 
scratching my head if I saw something like `[1, 2, 3].orElse(4)`. 
An array, or else a single int? How does that even type-check? 
Compare to `[1, 2, 3].frontOrElse(4)`, which is so obvious that 
it needs no explanation.

I understand the allure of this kind of "just works", "do what I 
mean" design, but following that philosophy is what gives us 
features like autodecoding--mostly harmless, but incredibly 
frustrating when they get in your way. After all, when someone 
iterates over a range of UTF-8 code units, it's "pretty obvious" 
that what they want is code points, right?

> So make coalescing separate from orElse-ing is basically the 
> gist of this right?

As I understand it, coalescing means flattening or unwrapping 
multiple layers of Optional at once. So, yes, orElse definitely 
shouldn't be doing that. But I'm guessing that's not what you 
meant to say.

> Sorry, what was the `some(val1.orElse(val2))`? Did you mean 
> that instead of range1.orElse(range2) or 
> range1.orElse(elementOfRange)?

Sorry, that was an incorrect example. What I was trying to say 
was, I don't think Optional.orElse should have two different 
return types depending on its arguments. If you want to have a 
version that unwraps its first argument and a version that 
doesn't, it would be better to give them different names, so it's 
always obvious which is being used. For example:

Optional!int a = no!int;
Optional!int b = some(123);

int x = a.valueOrElse(123);   // unwraps
Optional!int y = a.orElse(b); // doesn't unwrap

This is how they do it in Rust: the non-unwrapping version is 
called `or`, and the unwrapping version is called `unwrap_or`.


More information about the Digitalmars-d mailing list