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
}