October 12, 2014
On Sunday, 12 October 2014 at 08:36:05 UTC, Marc Schütz wrote:
> On Sunday, 12 October 2014 at 04:31:22 UTC, Walter Bright wrote:
>> On 10/11/2014 7:23 AM, IgorStepanov wrote:
>>> class A
>>> {
>>>   int i;
>>>   alias i this;
>>> }
>>>
>>> class B
>>> {
>>>   int i;
>>>   alias i this;
>>> }
>>>
>>> class C
>>> {
>>>   A a;
>>>   B b;
>>>   alias a this;
>>>   alias b this;
>>> }
>>>
>>> void foo(T)(T arg) if(is(T : int))
>>> {
>>>   ...
>>> }
>>>
>>> foo(C()); //Should it pass or not?
>>
>> There's a rule with imports that if the same symbol is reachable via multiple paths through the imports, that it is not an ambiguity error. Here, the same type is reachable through multiple alias this paths, so by analogy it shouldn't be an error.
>
> It's the same type, but different symbols; actual accesses would be ambiguous. `is(T : int)` shouldn't evaluate to true if `int a = T.init;` would fail.

I found an example of a situation that is bothering me.
Let we have a persistence framework, which provides a storing D object in some persistence storage: DB, file et c.

In introduces paired functions store/load and special type PersistenceObject.

If stored type is subtype of PersistenceObject it converts to PersistenceObject and PersistenceObject.load(stream) called for loading object (and PersistenceObject.store(stream) for storing).
Otherwice if object can't be converted to PersistenceObject it should be serialized via "serialize" function (or deserialized via "deserialize").

struct PersistenceFramework
{
   void store(T)(T arg) if (is(T : PersistenceObject))
   {
       PersistenceObject po = arg;
       arg.store(stream);
   }

   void store(T)(T arg) if (!is(T : PersistenceObject))
   {
       PersistenceObject po = arg;
       store(serialize(arg));
   }

   void load(T)(ref T arg) if (is(T : PersistenceObject))
   {
       PersistenceObject po = arg;
       arg.load(stream);
   }

   void load(T)(ref T arg) if (!is(T : PersistenceObject))
   {
       PersistenceObject po = arg;
       load(serialize(arg));
   }

   Stream stream;
}

/****************************************************************
And we have the next types which we want to store and load
*****************************************************************/

struct Role
{
    ...
}

struct User
{
   Role role;
   PersistenceObject po;
   //...
   alias role this;
   alias po this;
}

/*****************************************************************/

User u;
persistenceFramework.load(u)
//...
persistenceFramework.store(u);


/******************************************************************/
Role is not subtype of PersistenceObject thus all works ok.
We can store User via User.po and load it again;

Some time later, Role designer decided that Role should be subtype of PersistenceObject and changed Role definition:

struct Role
{
    ...
    PersistenceObject po;
    alias po this;
}

Now, User can not be converted to PersistenceObject because there are two path to convert: User.po and User.role.po;
Storing code after this change will be copiled successfully (if we follow your "is" rule), however object will be tried to load via "void load(T)(ref T arg) if (!is(T : PersistenceObject))".
Because object was saved via "void store(T)(T arg) if (is(T : PersistenceObject))" at the previous program run, user will not be loaded succesfully. Moreover, you will get an strange unexpected program behaviour and will be hard to find real error cause.

/*****************************************************************/
And finally, if you want to check, if you Type _really_ can be converted to AnotherType, you can use the next check:

void foo(Type)(Type arg) if (is(typeof({AnotherType x = Type.init;})))
{

}

October 12, 2014
On 10/12/2014 06:28 AM, Walter Bright wrote:
> On 10/11/2014 3:42 AM, Jacob Carlborg wrote:
>> On 2014-10-11 00:52, Walter Bright wrote:
>>
>>> I like the C++ rule that says that access control is not considered for
>>> name lookup. I know it makes for some annoying results, but the
>>> simplicity of the rule makes it much more understandable.
>>
>> I'm not so sure about that. Perhaps it makes it more understandable for a
>> language designer. But not for a user. You try to call a function but
>> you get a
>> conflict with a private symbol. The user will get frustrated thinking:
>> "stupid
>> compiler, of course I want to call the public function".
>
> The theory is that simpler rules are better than complex rules, even if
> the simpler rules aren't always ideal.
>

Public symbols conflicting with private symbols are not just not ideal, they are a major PITA. The procedure for resolving ambiguities using alias introduces new private symbols itself!
October 12, 2014
On 10/10/14 6:10 PM, IgorStepanov wrote:
> On Friday, 10 October 2014 at 21:26:49 UTC, Steven Schveighoffer wrote:

>> An example:
>>
>> foo(T)(T t) if(is(T : int))
>> {
>>    someFuncThatTakesInt(t);
>> }
>>
>> foo(T)(T t) if(!is(T : int) && is(T.shadow : int))
>> {
>>    someFuncThatTakesInt(t.shadow);
>> }
>>
>> struct A
>> {
>>    int i;
>>    alias i this;
>> }
>>
>> struct B
>> {
>>    int i;
>>    alias i this;
>> }
>>
>> struct C
>> {
>>    A a;
>>    B shadow;
>>    alias a this;
>>    alias shadow this;
>> }
>>
>> C c;
>> foo(c); // should compile, but I think your DIP makes it fail due to
>> ambiguity
>>
>
> You can write foo(c.shadow); This isn't hard.
> Ok, I understood you, let's listen to what others say

Right, you can get around it.

But the issue here is, that I feel like is(T: U) means (from dlang.org):

is ( Type : TypeSpecialization )
The condition is satisfied if Type is semantically correct and it is the same as or can be implicitly converted to TypeSpecialization.

This means is(C : int) should indicate that C can implicitly convert to int. But in your DIP, it does not. I think this is incorrect.

-Steve
October 12, 2014
On Sunday, 12 October 2014 at 23:02:13 UTC, Steven Schveighoffer wrote:
> On 10/10/14 6:10 PM, IgorStepanov wrote:
>> On Friday, 10 October 2014 at 21:26:49 UTC, Steven Schveighoffer wrote:
>
>>> An example:
>>>
>>> foo(T)(T t) if(is(T : int))
>>> {
>>>   someFuncThatTakesInt(t);
>>> }
>>>
>>> foo(T)(T t) if(!is(T : int) && is(T.shadow : int))
>>> {
>>>   someFuncThatTakesInt(t.shadow);
>>> }
>>>
>>> struct A
>>> {
>>>   int i;
>>>   alias i this;
>>> }
>>>
>>> struct B
>>> {
>>>   int i;
>>>   alias i this;
>>> }
>>>
>>> struct C
>>> {
>>>   A a;
>>>   B shadow;
>>>   alias a this;
>>>   alias shadow this;
>>> }
>>>
>>> C c;
>>> foo(c); // should compile, but I think your DIP makes it fail due to
>>> ambiguity
>>>
>>
>> You can write foo(c.shadow); This isn't hard.
>> Ok, I understood you, let's listen to what others say
>
> Right, you can get around it.
>
> But the issue here is, that I feel like is(T: U) means (from dlang.org):
>
> is ( Type : TypeSpecialization )
> The condition is satisfied if Type is semantically correct and it is the same as or can be implicitly converted to TypeSpecialization.
>
> This means is(C : int) should indicate that C can implicitly convert to int. But in your DIP, it does not. I think this is incorrect.
>
> -Steve

Hmm. I've written case (my previous post), when returning false from is(T: S), where T has many pathes to S is dangerous. However your words also contain the truth. I don't know what we need to do. Maybe we should raise error during "is" semantic? Please, read my example and say your opinion.
October 12, 2014
Advantage of ky way is a more strictness then your way: if function with if(is(T: S)) will be called, error will be raised at the first trying of convert T to S. And we don't give the opportunity of possible error to spread away from the place of origin.
October 13, 2014
On 10/12/14 7:16 PM, IgorStepanov wrote:
> On Sunday, 12 October 2014 at 23:02:13 UTC, Steven Schveighoffer wrote:
>> On 10/10/14 6:10 PM, IgorStepanov wrote:

>>> You can write foo(c.shadow); This isn't hard.
>>> Ok, I understood you, let's listen to what others say
>>
>> Right, you can get around it.
>>
>> But the issue here is, that I feel like is(T: U) means (from dlang.org):
>>
>> is ( Type : TypeSpecialization )
>> The condition is satisfied if Type is semantically correct and it is
>> the same as or can be implicitly converted to TypeSpecialization.
>>
>> This means is(C : int) should indicate that C can implicitly convert
>> to int. But in your DIP, it does not. I think this is incorrect.

>
> Hmm. I've written case (my previous post), when returning false from
> is(T: S), where T has many pathes to S is dangerous.

OK, I didn't understand your case before, but I just got it.

I understand what you mean, but this isn't anything new -- one can cause weird problems by creating diamond-pattern interfaces also. I do not actually think it is dangerous, because one would not leave an error call in their code. So for a future change to a library to "mystically" make a function start working is not a danger, because said code wasn't sitting there broken in the first place.

I will note, that for diamond problem interfaces, the compiler seems to take a different track than your DIP:

interface A {}
interface B : A {}
interface C : A {}

class X : B, C {}

static assert(is(X : A));

void main()
{
    A a = new C; // works, not sure if it's B.A or C.A
}

I know this is a different problem -- we aren't pointing at two different concrete implementations.

-Steve
October 13, 2014
On Monday, 13 October 2014 at 00:54:13 UTC, Steven Schveighoffer wrote:
> On 10/12/14 7:16 PM, IgorStepanov wrote:
>> On Sunday, 12 October 2014 at 23:02:13 UTC, Steven Schveighoffer wrote:
>>> On 10/10/14 6:10 PM, IgorStepanov wrote:
>
>>>> You can write foo(c.shadow); This isn't hard.
>>>> Ok, I understood you, let's listen to what others say
>>>
>>> Right, you can get around it.
>>>
>>> But the issue here is, that I feel like is(T: U) means (from dlang.org):
>>>
>>> is ( Type : TypeSpecialization )
>>> The condition is satisfied if Type is semantically correct and it is
>>> the same as or can be implicitly converted to TypeSpecialization.
>>>
>>> This means is(C : int) should indicate that C can implicitly convert
>>> to int. But in your DIP, it does not. I think this is incorrect.
>
>>
>> Hmm. I've written case (my previous post), when returning false from
>> is(T: S), where T has many pathes to S is dangerous.
>
> OK, I didn't understand your case before, but I just got it.
>
> I understand what you mean, but this isn't anything new -- one can cause weird problems by creating diamond-pattern interfaces also. I do not actually think it is dangerous, because one would not leave an error call in their code. So for a future change to a library to "mystically" make a function start working is not a danger, because said code wasn't sitting there broken in the first place.
>
> I will note, that for diamond problem interfaces, the compiler seems to take a different track than your DIP:
>
> interface A {}
> interface B : A {}
> interface C : A {}
>
> class X : B, C {}
>
> static assert(is(X : A));
>
> void main()
> {
>     A a = new C; // works, not sure if it's B.A or C.A
> }
>
> I know this is a different problem -- we aren't pointing at two different concrete implementations.
>
> -Steve

This is fundamentally different situation: interfaces haven't a state, thus don't care what interface will be getted: B.C or C.C. Moreover, we can think that we have only one base C (like virtual inherited class in C++).
Alias this case requires a completely different approach.
October 13, 2014
>>> On 10/11/2014 7:23 AM, IgorStepanov wrote:
>>>> class A
>>>> {
>>>>  int i;
>>>>  alias i this;
>>>> }
>>>>
>>>> class B
>>>> {
>>>>  int i;
>>>>  alias i this;
>>>> }
>>>>
>>>> class C
>>>> {
>>>>  A a;
>>>>  B b;
>>>>  alias a this;
>>>>  alias b this;
>>>> }

My preferred solution would be to reject the 2nd alias declaration outright.

I don't see any value in intentionally creating the above pattern, _if_ it occurs then it's most likely due to an unintentional side-effect of a re-factoring, thus it should error out as close as possible to the real error.

October 14, 2014
On Monday, 13 October 2014 at 15:21:32 UTC, Daniel N wrote:
>>>> On 10/11/2014 7:23 AM, IgorStepanov wrote:
>>>>> class A
>>>>> {
>>>>> int i;
>>>>> alias i this;
>>>>> }
>>>>>
>>>>> class B
>>>>> {
>>>>> int i;
>>>>> alias i this;
>>>>> }
>>>>>
>>>>> class C
>>>>> {
>>>>> A a;
>>>>> B b;
>>>>> alias a this;
>>>>> alias b this;
>>>>> }
>
> My preferred solution would be to reject the 2nd alias declaration outright.
>
> I don't see any value in intentionally creating the above pattern, _if_ it occurs then it's most likely due to an unintentional side-effect of a re-factoring, thus it should error out as close as possible to the real error.

This code tell that C is subtype of A and C is subtype of B.
User can use this fact in his code:
void foo(B);

C c = new C;
foo(c); //Ok.
Of course, we shouldn't allow user to cast c to int:
int i = c; //wrong
However, user can explicitly cast c to his subtype, which is convertable to int:
int i = cast(B)c; //Ok
Summarizing, I disagree with suggestion disallow this code at type semantic stage.
October 15, 2014
On Tuesday, 14 October 2014 at 12:33:50 UTC, IgorStepanov wrote:
> This code tell that C is subtype of A and C is subtype of B.
> User can use this fact in his code:
> void foo(B);
>
> C c = new C;
> foo(c); //Ok.
> Of course, we shouldn't allow user to cast c to int:
> int i = c; //wrong
> However, user can explicitly cast c to his subtype, which is convertable to int:
> int i = cast(B)c; //Ok
> Summarizing, I disagree with suggestion disallow this code at type semantic stage.

I agree. It will also make possible to break already working disambugation of `foo(c)` kind but adding new `alias this` to one of subtypes independently. That sounds annoying.