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