November 19, 2018
On Monday, 19 November 2018 at 02:08:14 UTC, Dennis wrote:
> On Monday, 19 November 2018 at 01:24:02 UTC, Stanislav Blinov wrote:
>> Yup, that's because, like Rubn said, copying value types is trivial. Where it all comes to bite you is when you start having pointers, because you can't copy a const(T)* into a T*.
>
> I'm not using reference types, but still:
>
> ```
> struct S {
>     int a;
>     this(int a) {
>         this.a = a;
>     }
> }
>
> void main()
> {
>     immutable S d = 3;
> }
>
> ```
>
> onlineapp.d(10): Error: mutable method onlineapp.S.this is not callable using a immutable object
> onlineapp.d(10):        Consider adding const or inout to onlineapp.S.this
>
> const still leaves the first error...

You're skimming the examples ;)

struct S {
    int a;
    this(int a) { this.a = a; }
    this(int a) const { this.a = a; }
    this(int a) immutable { this.a = a; }
}

or

struct S {
    int a;
    this(this T)(int a) { this.a = a; }
}

> ...inout works though I don't know what it does.

Recall that member functions (including constructors) are just functions in disguise:

struct S {
    this(int a) inout { /* ... */ }
}

what that boils down to, conceptually, is:

void _compiler_made_it_a_struct_S_constructor(ref inout S this, int a);

In other words, an `inout` method makes the "this" reference an `inout`. Same goes for const, immutable, etc.
November 19, 2018
On Monday, 19 November 2018 at 02:39:32 UTC, Stanislav Blinov wrote:
> You're skimming the examples ;)

I'm not saying your examples don't work, I'm trying to understand the minimum requirements. You said:

"That's [constructors needing to be pure is] only for types with indirections (pointers), since `pure` guarantees that you do not mutate any global state."

My example was only to show that:
- a const constructor is insufficient for creating an immutable struct S
- an immutable or pure constructor is sufficient for creating an immutable struct S

You also showed that an inout constructor is sufficient too.

I don't understand why not any constructor would work. After all:

"value types are all copyable between mutable/const/immutable. So even if `return x + y` would yield a `const T`, you can still instantiate a T from it."

> Recall that member functions (including constructors) are just functions in disguise:
> ...
> what that boils down to, conceptually, is:

I get what inout does know. But continuing on the constructor lowering, what strikes me is that:

S __ctor(ref const S this, int x) {
    this.x = x; // allowed, while `this` is a const(S)!
    // return this; automatically inserted
}

Knowing that a constructor may mutate the const(this) or immutable(this) members, why can't a mutable(this) constructor be called on an immutable(this), *unless* it is pure (which seems irrelevant to me)?
November 19, 2018
On Monday, 19 November 2018 at 12:28:43 UTC, Dennis wrote:
> On Monday, 19 November 2018 at 02:39:32 UTC, Stanislav Blinov wrote:
>> You're skimming the examples ;)
>
> I'm not saying your examples don't work, I'm trying to understand the minimum requirements. You said:
>
> "That's [constructors needing to be pure is] only for types with indirections (pointers), since `pure` guarantees that you do not mutate any global state."
>
> My example was only to show that:
> - a const constructor is insufficient for creating an immutable struct S
> - an immutable or pure constructor is sufficient for creating an immutable struct S

Yes, that's what I meant.

> You also showed that an inout constructor is sufficient too.
>
> I don't understand why not any constructor would work. After all:
>
> "value types are all copyable between mutable/const/immutable. So even if `return x + y` would yield a `const T`, you can still instantiate a T from it."

Because the rules still hold:

this(int) -> __ctor(T, int);
this(int) const -> __ctor(const T, int);
this(int) immutable -> __ctor(immutable T, int);

>> Recall that member functions (including constructors) are just functions in disguise:
>> ...
>> what that boils down to, conceptually, is:
>
> I get what inout does know. But continuing on the constructor lowering, what strikes me is that:
>
> S __ctor(ref const S this, int x) {
>     this.x = x; // allowed, while `this` is a const(S)!
>     // return this; automatically inserted
> }
>
> Knowing that a constructor may mutate the const(this) or immutable(this) members, why can't a mutable(this) constructor be called on an immutable(this), *unless* it is pure (which seems irrelevant to me)?

Because it's not mutation, it's initialization. Just like you write:

const int x = 1; // initializes a const int
x = 10; // error, can't mutate

...the first assignment to a member in a constructor is initialization:

struct S {
    int x;
    this(int x) immutable {
        this.x = x; // ok, initialization
        this.x = 5; // error, can't mutate
    }
}

When a ctor is `pure`, the compiler knows it doesn't mutate any state other than the object's, so it allows conversion to all three qualifiers.

What you can do, however, if you don't have an const/immutable constructor, is call a mutable constructor explicitly, so long as conversion is possible (i.e. value types):

struct S {
    this(int) /* not immutable */ {}
}

immutable S x = 1; // error
immutable S x = S(1); // ok
November 19, 2018
On Monday, 19 November 2018 at 13:34:50 UTC, Stanislav Blinov wrote:
> Because it's not mutation, it's initialization.

Ohhhhh. That's an epiphany for me.

> When a ctor is `pure`, the compiler knows it doesn't mutate any state other than the object's, so it allows conversion to all three qualifiers.

I had trouble thinking of an example where impure constructors would break immutable, but now I get it:

```
int* gPtr;

struct S {
  int a;
  this(int a) {
    this.a = a;
    gPtr = &this.a;
  }
}

void main() {
  S s = 1;
  (*gPtr)++;
}
```
s can't be immutable because its field could be mutated, so the constructor needs to be pure (so gPtr can't be set) or immutable (so &this.a is an immutable(int)*).

> What you can do, however, if you don't have an const/immutable constructor, is call a mutable constructor explicitly, so long as conversion is possible (i.e. value types)

Interesting.


November 19, 2018
On 11/18/18 1:17 PM, Stanislav Blinov wrote:
> On Sunday, 18 November 2018 at 17:30:18 UTC, Dennis wrote:
>> I'm making a fixed point numeric type and want it to work correctly with const. First problem:
>>
>> ```
>> const q16 a = 6;
>> a /= 2;          // compiles! despite `a` being const.
> 
> Ouch. That's actually kind of nasty.
> 
>> writeln(a);      // still 6
>> a.toQ32 /= 2;    // what's actually happening
>> ```
>>
>> My q16 type has an implicit conversion to q32 (like how int can be converted to long):
>> ```
>> q32 toQ32() const {
>>   return q32(...);
>> }
>> alias toQ32 this;
>> ```
>> How do I make it so that a const(q16) will be converted to a const(q32) instead of mutable q32?
> 
> Like this:
> 
>          // implement separate methods for mutable/const/immutable
>          q32 toQ32() {
>              return q32(x);
>          }
> 
>          const(q32) toQ32() const {
>              return q32(x);
>          }
> 
>          immutable(q32) toQ32() immutable {
>              return q32(x);
>          }
> 
> Or like this:
> 
>          // implement all three in one method, using the `this template` feature
>          auto toQ32(this T)() {
>              static if (is(T == immutable))
>                  return immutable(q32)(x);
>              else static if (is(T == const))
>                  return const(q32)(x);
>              else
>                  return q32(x);
>          }

Or just use inout. This is literally what inout is for:

inout(q32) toQ32 inout {
    return q32(x);
}

This should transfer whatever constancy of the original is used for the return value.

However, I'd state that this is really a workaround for a language deficiency. In reality, I would surmise (without knowing the implementation) that q32's state is a complete copy of the q16 state. So there is no reason to apply any constancy copying from the source to the destination.

The real problem is that mutating operators on struct rvalues are always allowed, because `this` is always passed by reference. For the most part this is a harmless drawback, but because there is no way to "opt out" of this, you can't stop it when it really doesn't make sense (as in this case).

>> Second problem:
>> ```
>> Q log2(Q)(Q num) if (is(Q : q16) || is(Q : q32)) {
>>     import std.traits: Unqual;
>>     Unqual!Q x = num;
>>     // actual code
>> }
>> ```
>> When I call this with a const(q16), Q is resolved to const(q16) so I have to unqualify Q every time. It works, but feels clumsy. Is there an easier way to automatically de-const parameters? We're working with small value types here, it should be simple.
> 
> Define different overloads for Q and const Q. Or this:
> 
> Q log2(Q)(inout Q num) if (is(Q : q16) || is(Q : q32)) { /* ... */ }
> 
> Being able to jam mutable/const/immutable implementation in one function like that should tell you that you shouldn't mutate the argument. Then, the necessity to Unqual will go away on it's own ;)

I have long wanted a way to direct IFTI how to define its parameters base on the arguments. We have a simple adjustment for arrays, where the array is always unqual'd before IFTI define the parameter.

In other words:

const int[] arr;

void foo(T)(T t) {... }

foo(arr) => T = const(int)[], not const(int[])

I think Andrei in the last dconf proposed we do more of this with other types (for ranges, specifically). But I think it would be good to also define other conversions possibly manually.

-Steve
November 19, 2018
On Monday, 19 November 2018 at 14:04:29 UTC, Dennis wrote:
> On Monday, 19 November 2018 at 13:34:50 UTC, Stanislav Blinov wrote:
>> Because it's not mutation, it's initialization.
>
> Ohhhhh. That's an epiphany for me.

:)

>> When a ctor is `pure`, the compiler knows it doesn't mutate any state other than the object's, so it allows conversion to all three qualifiers.
>
> I had trouble thinking of an example where impure constructors would break immutable, but now I get it:
>
> ```
> int* gPtr;
>
> struct S {
>   int a;
>   this(int a) {
>     this.a = a;
>     gPtr = &this.a;
>   }
> }
>
> void main() {
>   S s = 1;
>   (*gPtr)++;
> }
> ```
> s can't be immutable because its field could be mutated, so the constructor needs to be pure (so gPtr can't be set) or immutable (so &this.a is an immutable(int)*).

Yep. Or the other way around (as I said earlier, it really makes sense when you have indirections inside the struct):

int* global;

struct S {
     const(int)* a;
     this(bool b) {
         if (b) a = global;
         else a = new int;
     }
}

You obviously can't instantiate an immutable S with that ctor, since const(int)* may point to mutable data. Nor can you make that ctor `pure`. However this is fine:

immutable(int*) global; // note it's an immutable(int*), not an immutable(int)*

struct S {
     const(int)* a;
     this(bool b) pure {
         if (b) a = global;
         else a = new int;
     }
}
November 19, 2018
On Monday, 19 November 2018 at 14:51:14 UTC, Steven Schveighoffer wrote:

> Or just use inout. This is literally what inout is for:
>
> inout(q32) toQ32 inout {
>     return q32(x);
> }
>
> This should transfer whatever constancy of the original is used for the return value.

Yep, I just wanted to explicitly point out the `this T`, which gets overlooked way too often.

> However, I'd state that this is really a workaround for a language deficiency. In reality, I would surmise (without knowing the implementation) that q32's state is a complete copy of the q16 state. So there is no reason to apply any constancy copying from the source to the destination.
> The real problem is that mutating operators on struct rvalues are always allowed, because `this` is always passed by reference. For the most part this is a harmless drawback, but because there is no way to "opt out" of this, you can't stop it when it really doesn't make sense (as in this case).

Sure. At first I was perplexed why Dennis' a /= 2 even compiled. Then I saw the alias this.

> I have long wanted a way to direct IFTI how to define its parameters base on the arguments. We have a simple adjustment for arrays, where the array is always unqual'd before IFTI define the parameter.
>
> In other words:
>
> const int[] arr;
>
> void foo(T)(T t) {... }
>
> foo(arr) => T = const(int)[], not const(int[])
>
> I think Andrei in the last dconf proposed we do more of this with other types (for ranges, specifically). But I think it would be good to also define other conversions possibly manually.

I agree completely. Like Dennis' code, or that gcd example from Phobos, it'd really help to be able to be explicit in the signature, not creative in implementation :) Especially considering that Unqual is a high-yield type system nuke.

1 2
Next ›   Last »