June 13, 2017
On 6/12/17 3:36 PM, H. S. Teoh via Digitalmars-d-learn wrote:
> On Mon, Jun 12, 2017 at 07:38:44PM +0000, Gary Willoughby via Digitalmars-d-learn wrote:
>> In the following code is there any way to make the `opBinary` method
>> generic to be able to accept immutable as well as a standard type? The
>> code currently passes the unit test but I wonder if I could get rid of
>> the duplication to overload the operator? I'm failing badly.
>
> This is what inout was designed for:
>
>  	public inout Rational opBinary(string op)(inout Rational rhs)
>  	{
>  		static if (op == "+")
>  		{
>  			return inout(Rational)(0, 0);
>  		}
>  		else
>  		{
>  			static assert(0, "Operator '" ~ op ~ "' not implemented");
>  		}
>  	}
>
> That should do the trick.

Nope, const works just fine. A clue is in your return type -- it's not inout!

This should work:

public Rational opBinary(string op)(Rational rhs) const

If Rational had any indirections, then inout would be required, and your signature wouldn't work (because you can't convert an inout(SomethingWithPointers) to SomethingWithPointers).

Why do I need to make the member function const, but not the parameter? Because the parameter is taken by value, 'this' is a reference.

-Steve
June 13, 2017
On Tuesday, 13 June 2017 at 11:36:45 UTC, Steven Schveighoffer wrote:
>
> Nope, const works just fine. A clue is in your return type -- it's not inout!
>
> This should work:
>
> public Rational opBinary(string op)(Rational rhs) const
>
> If Rational had any indirections, then inout would be required, and your signature wouldn't work (because you can't convert an inout(SomethingWithPointers) to SomethingWithPointers).
>
> Why do I need to make the member function const, but not the parameter? Because the parameter is taken by value, 'this' is a reference.
>
> -Steve

Is it possible for the `result` variable in the following code to be returned as an immutable type if it's created by adding two immutable types?

import std.stdio;

struct Rational
{
	public long numerator;
	public long denominator;

	public inout Rational opBinary(string op)(inout(Rational) other) const
	{
		static if (op == "+")
		{
			return inout(Rational)(0, 0);
		}
		else
		{
			static assert(0, "Operator '" ~ op ~ "' not implemented");
		}
	}
}

unittest
{
	auto baz    = immutable(Rational)(1, 3);
	auto qux    = immutable(Rational)(1, 6);
	auto result = baz + qux;

	writeln(typeof(result).stringof); // Result is not immutable!
}

June 13, 2017
On 06/13/2017 09:29 PM, Gary Willoughby wrote:
> Is it possible for the `result` variable in the following code to be returned as an immutable type if it's created by adding two immutable types?

Qualify the return type as `inout`:

    inout(Rational) opBinary(/*...*/)(/*...*/) inout {/*...*/}

(That second `inout` is the same as the first one in your code.)

Then you get a mutable/const/immutable result when you call the method on a mutable/const/immutable instance.

It doesn't matter a lot, though. `Rational` is a value type, so you can convert the return value between qualifiers as you want, anyway. But it affects what you get with `auto`, of course.

[...]
> struct Rational
> {
>      public long numerator;
>      public long denominator;
> 
>      public inout Rational opBinary(string op)(inout(Rational) other) const

`inout` and `const` kinda clash here. Both apply to `this`. But apparently, the compiler thinks `inout const` is a thing. No idea how it behaves. I don't think it's a thing in the language. As far as I understand, you should only put one of const/immutable/inout.
June 13, 2017
On 6/13/17 3:58 PM, ag0aep6g wrote:
> On 06/13/2017 09:29 PM, Gary Willoughby wrote:
>> Is it possible for the `result` variable in the following code to be
>> returned as an immutable type if it's created by adding two immutable
>> types?
>
> Qualify the return type as `inout`:
>
>     inout(Rational) opBinary(/*...*/)(/*...*/) inout {/*...*/}
>
> (That second `inout` is the same as the first one in your code.)
>
> Then you get a mutable/const/immutable result when you call the method
> on a mutable/const/immutable instance.
>
> It doesn't matter a lot, though. `Rational` is a value type, so you can
> convert the return value between qualifiers as you want, anyway. But it
> affects what you get with `auto`, of course.

Yes exactly. I prefer personally to have value types always return mutable. You can always declare the result to be immutable or const, but declaring mutable is not as easy. e.g.:

immutable ReallyLongValueTypeName foo1();
ReallyLongValueTypeName foo2();

version(bad)
{
   auto x = foo1;
   ReallyLongValueTypeName y = foo1;
}
version(good)
{
   immutable x = foo2;
   auto y = foo2;
}

>
> [...]
>> struct Rational
>> {
>>      public long numerator;
>>      public long denominator;
>>
>>      public inout Rational opBinary(string op)(inout(Rational) other)
>> const
>
> `inout` and `const` kinda clash here. Both apply to `this`. But
> apparently, the compiler thinks `inout const` is a thing. No idea how it
> behaves. I don't think it's a thing in the language. As far as I
> understand, you should only put one of const/immutable/inout.

const(inout) actually *is* a thing :)

It's a type constructor that can be implicitly cast from immutable. This has advantages in some cases.

See (horribly written) table at the bottom if the inout function section here: http://dlang.org/spec/function.html#inout-functions

Also I talked about it last year at Dconf 2016: http://dconf.org/2016/talks/schveighoffer.html

However, I find it very surprising that inout and const can combine the way you have written it. Even though it does make logical sense. I'd suspect it would be a good candidate for a "you didn't really mean that" error from the compiler, but it might be hard to decipher from the parsed code.

-Steve
June 13, 2017
On 06/13/2017 10:50 PM, Steven Schveighoffer wrote:
> const(inout) actually *is* a thing :)
> 
> It's a type constructor that can be implicitly cast from immutable. This has advantages in some cases.
> 
> See (horribly written) table at the bottom if the inout function section here: http://dlang.org/spec/function.html#inout-functions
> 
> Also I talked about it last year at Dconf 2016: http://dconf.org/2016/talks/schveighoffer.html

Huh. There it is.

Took me some experimenting to understand what it does. If anyone else is interested, this is where it clicked for me:

----
inout(int*) f(inout int* x, inout int* y)
{
    return y;
}

inout(int*) g(inout int* x)
{
    immutable int* y;
    return f(x, y); /* Error: cannot implicitly convert expression f(x, y) of type inout(const(int*)) to inout(int*) */
}
----

That code can't compile because g's inout return type says that it has to return a mutable result for a mutable argument x. But y is immutable, so that can't work.

We see in the error message that `f(x, y)` results in a `inout(const int*)`. We can change g's return type to that (or `auto`) and the code compiles. `inout const` enforces that the result is const or immutable. It cannot be mutable.

This raises a question: There is no analogous inout variant that goes the other way (to mutable and const but not immutable), is there?

This code doesn't work, and I see no way to make it work:

----
inout(int*) f(inout int* x, inout int* y)
{
    return y;
}

auto g(inout int* x)
{
    int* y;
    return f(x, y);
}

void main()
{
    int* x;
    int* r1 = g(x);
}
----
June 13, 2017
On 6/13/17 5:58 PM, ag0aep6g wrote:
> On 06/13/2017 10:50 PM, Steven Schveighoffer wrote:
>> const(inout) actually *is* a thing :)
>>
>> It's a type constructor that can be implicitly cast from immutable.
>> This has advantages in some cases.
>>
>> See (horribly written) table at the bottom if the inout function
>> section here: http://dlang.org/spec/function.html#inout-functions
>>
>> Also I talked about it last year at Dconf 2016:
>> http://dconf.org/2016/talks/schveighoffer.html
>
> Huh. There it is.
>
> Took me some experimenting to understand what it does. If anyone else is
> interested, this is where it clicked for me:
>
> ----
> inout(int*) f(inout int* x, inout int* y)
> {
>     return y;
> }
>
> inout(int*) g(inout int* x)
> {
>     immutable int* y;
>     return f(x, y); /* Error: cannot implicitly convert expression f(x,
> y) of type inout(const(int*)) to inout(int*) */
> }
> ----
>
> That code can't compile because g's inout return type says that it has
> to return a mutable result for a mutable argument x. But y is immutable,
> so that can't work.
>
> We see in the error message that `f(x, y)` results in a `inout(const
> int*)`. We can change g's return type to that (or `auto`) and the code
> compiles. `inout const` enforces that the result is const or immutable.
> It cannot be mutable.
>
> This raises a question: There is no analogous inout variant that goes
> the other way (to mutable and const but not immutable), is there?

No, the fact that immutable implicitly casts to const(inout) is a special property enabled by the knowledge that immutable data can NEVER change, so it's OK to assume it's (at least) const for all references. The same cannot be true of const or mutable.

>
> This code doesn't work, and I see no way to make it work:
>
> ----
> inout(int*) f(inout int* x, inout int* y)
> {
>     return y;
> }
>
> auto g(inout int* x)
> {
>     int* y;
>     return f(x, y);
> }
>
> void main()
> {
>     int* x;
>     int* r1 = g(x);
> }
> ----

This cannot work, because g() has no idea what the true mutability of x is. inout is not a template. This is why you can't implicitly cast inout to anything except const, and you can't implicitly cast anything to inout.

So it MUST return const(int *) (if you did auto r1 = g(x), typeof(r1) would be const(int *)).

-Steve
June 14, 2017
On 06/14/2017 12:45 AM, Steven Schveighoffer wrote:
> No, the fact that immutable implicitly casts to const(inout) is a special property enabled by the knowledge that immutable data can NEVER change, so it's OK to assume it's (at least) const for all references. The same cannot be true of const or mutable.

In other words: Immutable can't ever become mutable, at most it can become const.

In the same vein: Mutable can't ever become immutable, at most it can become const.

I don't see the fundamental difference. Mutable and immutable act very much alike. They're just on opposing ends of the scale.
>>
>> This code doesn't work, and I see no way to make it work:
>>
>> ----
>> inout(int*) f(inout int* x, inout int* y)
>> {
>>     return y;
>> }
>>
>> auto g(inout int* x)
>> {
>>     int* y;
>>     return f(x, y);
>> }
>>
>> void main()
>> {
>>     int* x;
>>     int* r1 = g(x);
>> }
>> ----
> 
> This cannot work, because g() has no idea what the true mutability of x is. inout is not a template. This is why you can't implicitly cast inout to anything except const, and you can't implicitly cast anything to inout.

I don't follow. `inout const` doesn't need a template to do its thing. I'm not sure what you mean about implicit conversions. Obviously, an inout result matches the corresponding inout argument(s). Doesn't matter if that's an implicit conversion or whatever.

The goal is something that works just like `inout const`, but switching mutable and immutable.

I've realized that my example can be simplified:

----
bool condition;
auto f(inout int* x)
{
    immutable int* y;
    return condition ? x : y;
}
void main()
{
    const r1 = f(new int);
    immutable r2 = f(new immutable int);
}
----

That's `inout const` at work. Put a mutable or const int* in, you get a const int* out. Put immutable in, you get immutable out. You can't get mutable out, because the function might return y which is immutable, and it cannot become mutable.

Now, this code could be made to work:

----
bool condition;
auto f(inout int* x)
{
    int* y; /* mutable now */
    return condition ? x : y;
}
void main()
{
    int* r1 = f(new int);
    const r1 = f(new immutable int);
}
----

Mutable in, mutable out. Const in, const out. Immutable in, const out. You can't get immutable out, because y is mutable, and it cannot become immutable. Same principle as above. You never go from mutable to immutable or the other way around, so everything's fine, no?

Except, there's no type in D that has this meaning.
June 13, 2017
On 6/13/17 7:51 PM, ag0aep6g wrote:
> On 06/14/2017 12:45 AM, Steven Schveighoffer wrote:
>> No, the fact that immutable implicitly casts to const(inout) is a
>> special property enabled by the knowledge that immutable data can
>> NEVER change, so it's OK to assume it's (at least) const for all
>> references. The same cannot be true of const or mutable.
>
> In other words: Immutable can't ever become mutable, at most it can
> become const.
>
> In the same vein: Mutable can't ever become immutable, at most it can
> become const.
>
> I don't see the fundamental difference. Mutable and immutable act very
> much alike. They're just on opposing ends of the scale.

The fundamental difference is that const and immutable share a characteristic that mutable doesn't -- you can't mutate the data.

The reason const(inout) works is because const(immutable) evaluates to just immutable.

const(<mutable>) just evaluates to const. In this way, mutable is less "special" than immutable.

>> This cannot work, because g() has no idea what the true mutability of
>> x is. inout is not a template. This is why you can't implicitly cast
>> inout to anything except const, and you can't implicitly cast anything
>> to inout.
>
> I don't follow. `inout const` doesn't need a template to do its thing.

Right, it's because the point at which the inout is "unwrapped" (i.e. at the point of return), it either becomes const or immutable. Inout functions can compile oblivious to how they will be called because the caller can completely determine the type that should be returned. It was the impetus to create inout in the first place -- why generate all these functions that do the same thing, just to change the return type, let the caller figure it out.

> I'm not sure what you mean about implicit conversions. Obviously, an
> inout result matches the corresponding inout argument(s). Doesn't matter
> if that's an implicit conversion or whatever.

There is actually a difference. inout wraps one and exactly one type modifier. The table in that function shows what the modifier is depending on your collection of mutability parameters.

If ALL of them are the same, it becomes that same thing.

If any are different, you use the table to figure out. Most differences become const, some special cases become const(inout). This special case is solely to allow a function that takes both immutable and inout to potentially return immutable instead of just const.

> Now, this code could be made to work:
>
> ----
> bool condition;
> auto f(inout int* x)
> {
>     int* y; /* mutable now */
>     return condition ? x : y;
> }
> void main()
> {
>     int* r1 = f(new int);
>     const r1 = f(new immutable int);
> }
> ----
>
> Mutable in, mutable out. Const in, const out. Immutable in, const out.
> You can't get immutable out, because y is mutable, and it cannot become
> immutable. Same principle as above. You never go from mutable to
> immutable or the other way around, so everything's fine, no?

The soundness of the function above seems good, but I don't know how to reason about the return type of f. Because mutable has no type modifier, it's hard to imagine doing this without one. And any time I think about how to define it, it breaks down. I can't imagine const(blah(T)) evaluating to mutable, no matter what blah is called, or how it works.

Literally the ONLY place this would be useful is for inout functions, and Andrei pretty much has declared that inout should be completely stricken from Phobos/druntime in favor of templates. I can't imagine any leeway for another type modifier.

const(inout) literally was an afterthought observation that we could relax the rules for this one case and get a little more usability.

-Steve
June 14, 2017
On 06/14/2017 03:47 AM, Steven Schveighoffer wrote:
> The fundamental difference is that const and immutable share a characteristic that mutable doesn't -- you can't mutate the data.

(... through the reference at hand.)

const and mutable share this: The data may be mutated from elsewhere.

Mutable shares as much with const as immutable does. But it's the other side of const, of course.

> The reason const(inout) works is because const(immutable) evaluates to just immutable.
> 
> const(<mutable>) just evaluates to const. In this way, mutable is less "special" than immutable.

Yeah. That makes it impossible to express the proposed type. But it's just because of the immutable > const > mutable progression in D, which makes for arbitrary asymmetries between mutable and immutable.

If it were const > mutable = immutable (and if we had a `mutable` keyword), then `inout immutable` would work like today's `inout const`, `inout mutable` would be what I'm talking about, and `inout const` would just be the same as const.

That's not going to happen, of course. It would break everything. And I'm sure there are many obvious issues that I'm just ignoring here because the thing won't happen anyway. But it would make `inout foo` more symmetric.

[...]
> The soundness of the function above seems good, but I don't know how to reason about the return type of f. Because mutable has no type modifier, it's hard to imagine doing this without one. And any time I think about how to define it, it breaks down. I can't imagine const(blah(T)) evaluating to mutable, no matter what blah is called, or how it works.
> 
> Literally the ONLY place this would be useful is for inout functions, and Andrei pretty much has declared that inout should be completely stricken from Phobos/druntime in favor of templates. I can't imagine any leeway for another type modifier.
> 
> const(inout) literally was an afterthought observation that we could relax the rules for this one case and get a little more usability.

Ok, I think we're in agreement. The described variant of inout would make sense, but isn't possible in D. It would be very awkward to implement the feature without changing how mutability qualifiers interact. And changing that would be way too disruptive. It's also not at all clear that it would go well with the rest of the language.
June 14, 2017
On Tuesday, 13 June 2017 at 19:29:26 UTC, Gary Willoughby wrote:
> Is it possible for the `result` variable in the following code to be returned as an immutable type if it's created by adding two immutable types?

Why do you even want that? Such plain data structure is implicitly convertible to any const flavor: https://dpaste.dzfl.pl/c59c4c7131b2
1 2
Next ›   Last »