www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Difficulties with std.variant.Algebraic

reply "Meta" <jared771 gmail.com> writes:
I've been playing around with std.variant.Algebraic, trying to 
define a very simple option type. However, I've been running into 
quite a few problems. My implementation is as follows:

import std.conv;
import std.variant;

struct Some(T)
{
     T payload;

     alias payload this;
}

struct None
{
}

struct Option(T)
{	
     Algebraic!(Some!T, None) payload;

     alias payload this;
}

Option!int unreliable(int val)
{
     if (val == 0)
     {
         return None();
     }
     else
     {
         return Some!int(val);
     }
}


Unfortunately, I can't return values of type Some or None from 
"unreliable" and expect the compiler to know that they should 
become an Algebraic wrapped in an Option. This is annoying, but I 
can't expect magic. Maybe if I'm explicit:

Option!int unreliable(int val)
{
     if (val == 0)
     {
         //Nope
	return cast(Option!int)None();
     }
     else
     {
         //Nope
	return cast(Option!int)Some!int(val);
     }
}

Option!int unreliable(int val)
{
     if (val == 0)
     {
         //Nope
	return cast(Option!int)None();
     }
     else
     {
         //Nope
	return cast(Option!int)Some!int(val);
     }
}

Option!int unreliable(int val)
{
     if (val == 0)
     {
         //Nope
	return cast(Algebraic!(Some!int, None))None();
     }
     else
     {
         //Nope
	return cast(Algebraic!(Some!int, None))Some!int(val);
     }
}


Okay, I'll try constructing a fresh struct and returning that:

Option!int unreliable(int val)
{
     if (val == 0)
     {
         //Nope
	return Option!int(None());
     }
     else
     {
         //Nope
	return Option!int(Some!int(val));
     }
}


One final, last-ditch effort:

Option!int unreliable(int val)
{
     if (val == 0)
     {
         return Option!int(cast(Algebraic!(Some!int, None))None());
     }
     else
     {
         return Option!int(cast(Algebraic!(Some!int, 
None))Some!int(val));
     }
}


Yes! It worked. The only problem is that this is completely 
unacceptable to type. The only other solution I can think of 
would be to define constructors for Option that take a Some and a 
None. I suppose it's not *all* bad, though. I mean, it compiles...

void main()
{
     auto maybeNone = unreliable(10);
     //Error: template std.variant.visit cannot deduce template 
function from argument types !()(Option!(int))
     assert(maybeNone.visit!((Some!int s) => text("Value is ", s),
                             (None n) => "No value")
                     == 10);
}


I don't know whether to laugh or cry.
Jul 10 2013
next sibling parent reply "Jesse Phillips" <Jesse.K.Phillips+D gmail.com> writes:
On Thursday, 11 July 2013 at 03:06:39 UTC, Meta wrote:
 struct Option(T)
 {	
     Algebraic!(Some!T, None) payload;

     alias payload this;
 }
This is untested but it probably looks something like this: private alias MaybeType = Algebraic!(Some!T, None); Option!int ans; ans.payload = MaybeType(None);
Jul 10 2013
parent reply "Meta" <jared771 gmail.com> writes:
On Thursday, 11 July 2013 at 04:03:19 UTC, Jesse Phillips wrote:
 On Thursday, 11 July 2013 at 03:06:39 UTC, Meta wrote:
 struct Option(T)
 {	
    Algebraic!(Some!T, None) payload;

    alias payload this;
 }
This is untested but it probably looks something like this: private alias MaybeType = Algebraic!(Some!T, None); Option!int ans; ans.payload = MaybeType(None);
Ideally, payload would be private, and only exposed through alias this. It's somewhat unfortunate that this is necessary in the first place. D doesn't happen to have something like an opImplicitCast, does it?
Jul 11 2013
parent reply "Jesse Phillips" <Jesse.K.Phillips+D gmail.com> writes:
On Thursday, 11 July 2013 at 12:05:49 UTC, Meta wrote:
 On Thursday, 11 July 2013 at 04:03:19 UTC, Jesse Phillips wrote:
 On Thursday, 11 July 2013 at 03:06:39 UTC, Meta wrote:
 struct Option(T)
 {	
   Algebraic!(Some!T, None) payload;

   alias payload this;
 }
This is untested but it probably looks something like this: private alias MaybeType = Algebraic!(Some!T, None); Option!int ans; ans.payload = MaybeType(None);
Ideally, payload would be private, and only exposed through alias this. It's somewhat unfortunate that this is necessary in the first place. D doesn't happen to have something like an opImplicitCast, does it?
opImplicitCast was rejected. As for private payload, this should also work. auto ans = Option!int(MaybeType(None())); I forgot to mention that most of the casting you were doing doesn't do what you think it would. If your types provided an opCast to the type you were requesting then that would have been used. struct Maybe(T) { Option!T payload; alias payload this; } This doesn't make an Option!int castable to a Maybe!int, they are still distinct types: struct Fish { int speed; } struct Dog { string name; } auto ans = cast(Fish) Dog(); And... compiler barf.
Jul 11 2013
next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Jesse Phillips:

 I forgot to mention that most of the casting you were doing 
 doesn't do what you think it would.
That's why I have suggested the OP to avoid to use cast() unless he/she knows well what she/he is doing. Bye, bearophile
Jul 11 2013
prev sibling parent reply "Meta" <jared771 gmail.com> writes:
On Thursday, 11 July 2013 at 18:31:50 UTC, Jesse Phillips wrote:
 I forgot to mention that most of the casting you were doing 
 doesn't do what you think it would.
 ...

 This doesn't make an Option!int castable to a Maybe!int, they 
 are still distinct types:
 ...
struct Test1 { int n; } struct Test2 { string s; Test1 t; alias t this; } void main() { auto t1 = Test1(); auto t2 = cast(Test2)t1; //Error } It seems you're right. That's disappointing. TDPL talks about alias this in terms of subtyping, but doesn't the above code show that this is not true subtyping?
Jul 11 2013
parent "Meta" <jared771 gmail.com> writes:
On Thursday, 11 July 2013 at 18:49:27 UTC, Meta wrote:
 It seems you're right. That's disappointing. TDPL talks about 
 alias this in terms of subtyping, but doesn't the above code 
 show that this is not true subtyping?
Actually, scratch that, I'm an idiot. I was thinking completely backwards.
Jul 11 2013
prev sibling parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Meta:

 I don't know whether to laugh or cry.
Algebraic has several problems, but your code has other problems. I suggest to take a look at Nullable, especially the version that makes a constant value the "null". Also try to almost never use cast() in your code, unless you _really_ know what you are doing. Expecting a bit of magic from the D compiler is OK. But someone has to ask for it in a clean way, someone has to implement it, and someone else has to accept it. Ask if you need more help. Bye, bearophile
Jul 11 2013
next sibling parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Some example code:

import std.typecons;

Nullable!int unreliable1(in int val) pure nothrow {
     if (val == 0) {
         return typeof(return)();
     } else {
         return typeof(return)(val);
     }
}

Nullable!(int, 0) unreliable2(in int val) pure nothrow {
     if (val == 0) {
         return typeof(return)();
     } else {
         return typeof(return)(val);
     }
}

void main() {
     auto nx1 = unreliable1(10);
     auto nx2 = unreliable2(10);
}


Bye,
bearophile
Jul 11 2013
parent "bearophile" <bearophileHUGS lycos.com> writes:
 Nullable!(int, 0) unreliable2(in int val) pure nothrow {
     if (val == 0) {
         return typeof(return)();
     } else {
         return typeof(return)(val);
     }
 }
Sorry, I meant: import std.typecons; Nullable!int unreliable1(in int val) pure nothrow { if (val == 0) { return typeof(return)(); } else { return typeof(return)(val); } } Nullable!(int, 0) unreliable2(in int val) pure nothrow { return typeof(return)(val); } void main() { auto nx1 = unreliable1(10); auto nx2 = unreliable2(10); } Bye, bearophile
Jul 11 2013
prev sibling parent reply "Meta" <jared771 gmail.com> writes:
On Thursday, 11 July 2013 at 12:30:17 UTC, bearophile wrote:
 Algebraic has several problems, but your code has other 
 problems.
Oh, no doubt. This isn't meant to be serious, industrial strength code.
 I suggest to take a look at Nullable, especially the version 
 that makes a constant value the "null".
Nullable will work in this case, but it doesn't solve the general problem with Algebraic.
 Also try to almost never use cast() in your code, unless you 
 _really_ know what you are doing.
I'm well aware of the dangers of cast. I was just playing around, trying to get this to work.
 Expecting a bit of magic from the D compiler is OK. But someone 
 has to ask for it in a clean way, someone has to implement it, 
 and someone else has to accept it.
Now that I think about it, I'm wondering exactly why the subtypes of Algebraic are not covariant with it when returned from a function. It works fine with other types that use alias this, e.g.: import std.variant; struct Test1 { } struct Test2 { Test1 t; alias t this; } Test1 covarReturn() { //Fine return Test2(); }
Jul 11 2013
parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Meta:

 Nullable will work in this case, but it doesn't solve the 
 general problem with Algebraic.
Then is this an acceptable solution? import std.variant; struct None {} template Option(T) { alias Option = Algebraic!(T, None); } Option!int unreliable(int val) pure nothrow { if (val == 0) { return typeof(return)(None()); } else { return typeof(return)(val); } } void main() {} Bye, bearophile
Jul 11 2013
parent reply "Meta" <jared771 gmail.com> writes:
On Thursday, 11 July 2013 at 16:48:00 UTC, bearophile wrote:
 Meta:

 Nullable will work in this case, but it doesn't solve the 
 general problem with Algebraic.
Then is this an acceptable solution? ...
That is a bit better, yes. Still somewhat clunky, but workable. It'd still be nice if it wasn't necessary. Why does it break when I use Some!int instead of just a bare int?
Jul 11 2013
next sibling parent "Meta" <jared771 gmail.com> writes:
On Thursday, 11 July 2013 at 17:11:05 UTC, Meta wrote:
 On Thursday, 11 July 2013 at 16:48:00 UTC, bearophile wrote:
 That is a bit better, yes. Still somewhat clunky, but workable. 
 It'd still be nice if it wasn't necessary. Why does it break 
 when I use Some!int instead of just a bare int?
Oh, right, I see. I missed the change from "struct" to "template".
Jul 11 2013
prev sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Meta:

 That is a bit better, yes. Still somewhat clunky, but workable.
Recently Walter has proposed a small improvement able to reduce the size of that template. I don't know where Walter proposal has gone in the meantime, as people have suggested a better and more general design.
 It'd still be nice if it wasn't necessary.
Algebraic is still primitive, but improving both D and Phobos it can become better. It requires some discussions, design, and implementation work. Bye, bearophile
Jul 11 2013