Re: An Optional!T and the implementation of the underlying type's opUnary

2018-07-27 Thread Simen Kjærås via Digitalmars-d

On Friday, 27 July 2018 at 12:52:09 UTC, aliak wrote:

On Thursday, 26 July 2018 at 06:37:41 UTC, Simen Kjærås wrote:
As for assigning to Optional!(immutable int), the language 
basically forbids this (cannot modify struct with immutable 
members). It would, as you say, cause problems when you can 
get a pointer to the contents.


So is this undefined behaviour?

import std.stdio;

struct S(T) {
T value;
void opUnary(string op)() inout {
mixin(op ~ "cast(T)value;");
}
}

void main() {
immutable a = S!int(2);
++a;
}


It's the exact same as the top two lines of this:

void main() {
immutable int a = 2;
++*cast(int*)
assert(a == 3); // Will trigger on DMD 2.081.1
}

So yes, it's casting away immutable and modifying it, which is UB.

--
  Simen


Re: An Optional!T and the implementation of the underlying type's opUnary

2018-07-27 Thread aliak via Digitalmars-d

On Thursday, 26 July 2018 at 06:37:41 UTC, Simen Kjærås wrote:

On Wednesday, 25 July 2018 at 21:59:00 UTC, aliak wrote:

It needs to work with const as well and immutable too.

immutable a = 3;
auto b = -a; // is ok, should be ok with the optional as well.

Plus T can be a custom type as well with "some" definition of 
opUnary. I can't seem to find any implementation guidelines 
either so I assume opUnary or any of the ops implementation 
details is implementation defined.


Template this[0] (and CopyTypeQualifiers[1])to the rescue!



Ah! Genius!

I had no idea that using TemplateThisParameters would not 
necessitate qualifying the function in question either.




As for assigning to Optional!(immutable int), the language 
basically forbids this (cannot modify struct with immutable 
members). It would, as you say, cause problems when you can get 
a pointer to the contents.


So is this undefined behaviour?

import std.stdio;

struct S(T) {
T value;
void opUnary(string op)() inout {
mixin(op ~ "cast(T)value;");
}
}

void main() {
immutable a = S!int(2);
++a;
}








Re: An Optional!T and the implementation of the underlying type's opUnary

2018-07-26 Thread Simen Kjærås via Digitalmars-d

On Wednesday, 25 July 2018 at 21:59:00 UTC, aliak wrote:

It needs to work with const as well and immutable too.

immutable a = 3;
auto b = -a; // is ok, should be ok with the optional as well.

Plus T can be a custom type as well with "some" definition of 
opUnary. I can't seem to find any implementation guidelines 
either so I assume opUnary or any of the ops implementation 
details is implementation defined.


Template this[0] (and CopyTypeQualifiers[1])to the rescue!

import std.traits: isPointer, CopyTypeQualifiers;

auto opUnary(string op, this This)()
if (__traits(compiles, (CopyTypeQualifiers!(This, T) t){ 
mixin("return "~op~"t;"); }))

{
alias U = CopyTypeQualifiers!(This, T);
static if (op == "*" && isPointer!T) {
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"));
}
}
}

unittest {
Optional!int a;
++a;
auto a2 = -a;
assert(!a2._hasValue);
a = some(3);
a++;

immutable b = Optional!int(3);
static assert(!__traits(compiles, ++b));
auto b2 = -b;
}

As for assigning to Optional!(immutable int), the language 
basically forbids this (cannot modify struct with immutable 
members). It would, as you say, cause problems when you can get a 
pointer to the contents.


[0]: https://dlang.org/spec/template.html#template_this_parameter
[1]: https://dlang.org/phobos/std_traits#CopyTypeQualifiers

--
  Simen


Re: An Optional!T and the implementation of the underlying type's opUnary

2018-07-25 Thread aliak via Digitalmars-d

On Wednesday, 25 July 2018 at 18:01:54 UTC, Atila Neves wrote:


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
}


It needs to work with const as well and immutable too.

immutable a = 3;
auto b = -a; // is ok, should be ok with the optional as well.

Plus T can be a custom type as well with "some" definition of 
opUnary. I can't seem to find any implementation guidelines 
either so I assume opUnary or any of the ops implementation 
details is implementation defined.


Re: An Optional!T and the implementation of the underlying type's opUnary

2018-07-25 Thread Atila Neves via Digitalmars-d

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
}