An Optional!T and the implementation of the underlying type's opUnary
Atila Neves
atila.neves at gmail.com
Wed Jul 25 18:01:54 UTC 2018
On Wednesday, 25 July 2018 at 12:51:08 UTC, aliak wrote:
> Hi, I'm working on an Optional!T type that is a mixture of
> Scala's Option[T] (i.e. range based) and Swift's and Kotlin's
> T? (i.e. safe dispatching). I'm interested in hearing about
> mutability concerns.
>
> So I want operations on the optional to dispatch to the
> underlying type T if it's present. So let's take opUnary as an
> example, this is how it's currently implemented:
>
> auto opUnary(string op)() {
> return this.opUnaryImpl!op();
> }
> auto opUnary(string op)() const {
> return this.opUnaryImpl!(op, const(T))();
> }
> auto opUnary(string op)() immutable {
> return this.opUnaryImpl!(op, immutable(T))();
> }
> private auto opUnaryImpl(string op, U = T)() const {
> import std.traits: isPointer;
> static if (op == "*" && isPointer!U) {
> import std.traits: PointerTarget;
> alias P = PointerTarget!U;
> return empty || front is null ? no!P :
> some(cast(P)*this._value);
> } else {
> if (empty) {
> return no!U;
> } else {
> return some(mixin(op ~ "cast(U)_value"));
> }
> }
> }
>
> (functions "some" and "no" are type constructors which return
> an Optional!T of whatever the argument type is - except "no"
> needs an explicit T argument)
>
> Why not "opUnary(string op)() inout"?
>
> The reason it's like this is because I want to transfer the
> constness of "this" to the value T that is stored inside. If I
> rewrite "opUnaryImpl()() const" as "opUnary()() inout" and
> remove the implementation for mutable, const, and immutable,
> then this works:
>
> immutable a = Optional!int(3);
> ++a;
>
> And the internal value is modified.
>
> Should that be allowed?
>
> The caveat is that 1) I want Optional!T to be nogc compatible.
> So therefore the value is stored similarly to this PR in phobos
> [1] (also for an Optional type)
>
> And 2) Optional!T provides an "unwrap" function that returns a
> T (if T is a class or interface), or a T*. So, if I allow
> modification by using inout on opUnary, then for the sake of
> consistency, I should be able to do this:
>
> immutable a = Optional!int(3);
> a = 4;
>
> But I can't do this because Optional.opAssign would be either
> inout or immutable and I can't modify this.value = newValue;
>
> And then what about:
>
> auto a = Optional(immutable int)(3);
> a = 3; // should this be allowed?
>
> If it is allowed then this will fail because of the nogc
> requirement:
>
> unittest {
> Optional!(immutable int) a = some!(immutable int)(5);
> immutable(int)* p = a.unwrap;
> assert(*p == 5);
> a = 4;
> assert(*a.unwrap == 4);
> assert(*p == 5);
> }
>
> Comments, suggestions, opinions?
>
> Cheers,
> - Ali
>
> [1] https://github.com/dlang/phobos/pull/3915
This works for me:
struct Optional(T) {
private T _value;
private bool empty = true;
this(T value) {
_value = value;
empty = false;
}
auto opUnary(string op)() {
if(!empty) mixin(op ~ "_value;");
return this;
}
string toString() const {
import std.conv: text;
return empty
? "None!" ~ T.stringof
: text("Some!", T.stringof, "(", _value.text, ")");
}
}
void main() {
import std.stdio;
Optional!int nope;
writeln(nope);
auto mut = Optional!int(3);
++mut; // compiles
writeln(mut);
immutable imut = Optional!int(7);
// ++imut; // error
}
More information about the Digitalmars-d
mailing list