Optional and orElse: design feedback/critique?

aliak something at something.com
Sat Jul 27 16:39:21 UTC 2019


On Saturday, 27 July 2019 at 15:07:56 UTC, Paul Backus wrote:
> On Saturday, 27 July 2019 at 13:17:32 UTC, aliak wrote:
>> * Null pointers are valid objects, so some!(int*)(null) is non 
>> empty
>> * Null classses and interfaces are empty, so some!Class(null) 
>> is empty
>
> I think it would be better to avoid weird special cases like 
> this. The optional package already includes a NotNull type, so 
> users can write `Optional!(NotNull!Class)` to opt into your 
> proposed semantics explicitly.

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.

>
>> orElse semantics:
>> =================
>>
>> [...]
>>
>> 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.
>
> My first impression, upon reading this, is that I'm going to 
> have to refer to the documentation every time I use `orElse` in 
> order to be sure I'm doing it right. To me, this is a smell: if 
> it takes me this long to describe what ought to be a very 
> simple utility function, I can tell I've made it too 
> complicated.

Very fair point. What if the description was simplified?

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?

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

>
> As far as concrete advice: the overloads for reference types 
> and ranges seem completely unrelated to Optional, so it seems 
> like an odd choice to include them in the "optional" package. 
> The overloads for Optional and Nullable make sense to include 
> (though the Nullable one really *ought* to be in Phobos), but 
> special-casing them to wrap the return value is too "magic" for

This is true. They are kinda unrelated and just there for 
convenience because I didn't know where else to get the 
functionality from. I actually played around with having the 
orelse in a completely separate package, and providing a hook:

auto ref orElse(alias elseValue, T)(auto ref T value) {
   static if (hasMember!(T, "hookOrElse") {
     return value.hookOrElse!elseValue;
   }
   ...
}

And then implementing the hook in the optional package. But this 
bit me: 
https://forum.dlang.org/thread/lddwnnzlktszwspldxqu@forum.dlang.org

> my tastes. Much better to just let people write 
> `some(val1.orElse(val2))` if that's what they want--it's only a 
> few more characters, and it makes it obvious at a glance what 
> the code is doing.

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

And thanks for the feedback!




More information about the Digitalmars-d mailing list