February 02, 2012 [Issue 7355] inout incorrectly resolved if the same type has both mutable and immutable parts | ||||
---|---|---|---|---|
| ||||
Posted in reply to timon.gehr@gmx.ch | http://d.puremagic.com/issues/show_bug.cgi?id=7355 --- Comment #10 from timon.gehr@gmx.ch 2012-02-02 13:05:31 PST --- (In reply to comment #9) > (In reply to comment #8) > > This rule should work satisfactory: > > > > - The compiler is required to resolve inout such that the behavior is as if > > there were four (five, with inout(const(T)) enhancement in place) separate > > overloads. This could be implemented similar to how you propose it, by adding > > all of the versions to the overload set, or by using some insights to speed up > > the process (not very hard) > > This sounds fine. However, inout(const(T)) is not a substitute for inout(T), > so it should be four. > inout(const(T)) should be its own type. And as soon as that is the case, inout const will have to be a valid substitute for inout. For example, this should type check: immutable(int)[] arr = [2,3,4]; inout(const(int))[] foo(inout(int)[] x){ return uniform(0,2) ? arr : x; } inout(int)[] id(inout(int)[] x){ return x; } inout(int)[] bar(inout(int)[] x){ inout(int)[] y = [1,2,3]; inout(const(int))[] z = id(foo(y)); // inout const substitute for inout return z~x; } > inout(const(T)) is special in what can implicitly convert to it. But the inout > is the only wildcard there. True, but that does not mean it should not be a valid substitute for inout. > > The rest is not necessary. The normal overload rules already should handle which one is chosen. Since inout, mutable, and immutable do not implicitly convert to each other, it's not possible for there to be an ambiguity, is there? immutable and mutable (and by extension inout) should be preferred over const. There are indeed corner cases, for example: void foo(immutable(int) x, float y){} void foo(const(int) x, float y){} void foo(int x, float y){} void main(){foo(1,1);} // error, matches all three A different solution would be to refine the overload rules. > > It's important to note that the inout overload we are talking about is not the wildcard inout, but the local const-like inout. Exactly. The same holds for inout(const(T)). -- Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email ------- You are receiving this mail because: ------- |
February 03, 2012 [Issue 7355] inout incorrectly resolved if the same type has both mutable and immutable parts | ||||
---|---|---|---|---|
| ||||
Posted in reply to timon.gehr@gmx.ch | http://d.puremagic.com/issues/show_bug.cgi?id=7355 --- Comment #11 from Steven Schveighoffer <schveiguy@yahoo.com> 2012-02-03 03:24:52 PST --- (In reply to comment #10) > For example, this should type check: > > immutable(int)[] arr = [2,3,4]; > inout(const(int))[] foo(inout(int)[] x){ > return uniform(0,2) ? arr : x; > } > inout(int)[] id(inout(int)[] x){ > return x; > } > inout(int)[] bar(inout(int)[] x){ > inout(int)[] y = [1,2,3]; > inout(const(int))[] z = id(foo(y)); // inout const substitute for inout > return z~x; > } This typechecks even if we don't have inout(const) as an option: inout matches inout (we need a new term for this 'local' inout!) foo(y) => inout(int)[] inout(int)[] => inout(const(int))[] => z Besides, you are assuming here that the lvalue side of the expression plays a role in determining the inout match. It doesn't. The return type is solely determined by the argument expressions. > There are indeed corner cases, for example: > > void foo(immutable(int) x, float y){} > void foo(const(int) x, float y){} > void foo(int x, float y){} > > void main(){foo(1,1);} // error, matches all three This case has no relevance, there is no inout return value. Who cares what inout resolves to? Not to be nit picky, but we should consider that any polysemous value type is not going to play a vital role in an inout function, since it's implicitly convertible to any modifier. It's references or contained references which make a difference. If it comes down to supporting this, choosing any inout match arbitrarily is good enough. -- Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email ------- You are receiving this mail because: ------- |
February 03, 2012 [Issue 7355] inout incorrectly resolved if the same type has both mutable and immutable parts | ||||
---|---|---|---|---|
| ||||
Posted in reply to timon.gehr@gmx.ch | http://d.puremagic.com/issues/show_bug.cgi?id=7355 --- Comment #12 from timon.gehr@gmx.ch 2012-02-03 14:42:21 PST --- (In reply to comment #11) > (In reply to comment #10) > > For example, this should type check: > > > > immutable(int)[] arr = [2,3,4]; > > inout(const(int))[] foo(inout(int)[] x){ > > return uniform(0,2) ? arr : x; > > } > > inout(int)[] id(inout(int)[] x){ > > return x; > > } > > inout(int)[] bar(inout(int)[] x){ > > inout(int)[] y = [1,2,3]; > > inout(const(int))[] z = id(foo(y)); // inout const substitute for inout > > return z~x; > > } > > This typechecks even if we don't have inout(const) as an option: > [snip.] > My point was that it does not _if_ we have it as an option (and it has to be an option if we want inout to be powerful enough to replace identical overloads on const). Furthermore, foo cannot type check without inout(const) and provide the same guarantees. Maybe my example was not illustrative enough, second try: immutable(int)[] arr = [2,3,4]; inout(const(int))[] foo(inout(int)[] x){ return uniform(0,2) ? arr : x; } inout(int)[] id(inout(int)[] x){ return x; } inout(const(int))[] bar(inout(int)[] x){ inout(int)[] y = [1,2,3]; return id(foo(y)); // !!!! } void main(){ auto a = new int[10]; auto b = bar(a); static assert(is(typeof(b)==const(int)[])); // !!! auto c = new immutable(int)[10]; immutable(int)[] d = bar(c); // !!! } foo(y) => inout(const(int))[] id(foo(y)) =>! inout(const(int))[]// has to use inout const as inout substitute! resolve inout to mutable or const: return type inout(const(int))[] => const(int)[] resolve inout to immutable: return type inout(const(int))[] => immutable(int)[] > Besides, you are assuming here that the lvalue side of the expression plays a role in determining the inout match. It doesn't. The return type is solely determined by the argument expressions. > I don't see why you think I am. > > There are indeed corner cases, for example: > > > > void foo(immutable(int) x, float y){} > > void foo(const(int) x, float y){} > > void foo(int x, float y){} > > > > void main(){foo(1,1);} // error, matches all three > > This case has no relevance, there is no inout return value. Who cares what inout resolves to? Nobody. You claimed that the overload resolution rules are good enough to uniquely determine what inout should resolve to in all cases, and I provided a minimal counter-example. If you want something a little bit more concrete, consider this: immutable(int)[] foo(immutable(int)[] x, float f){return x;} int[] foo(int[] x, float f){return x;} void main(){foo([1,2,3],4);} // error I am becoming quite confident that the overload resolution rules are the culprit. A parameter type should be considered more specialized than another one if it better matches the argument type. That would render the disambiguation steps in my sketch unnecessary. > > Not to be nit picky, but we should consider that any polysemous value type is not going to play a vital role in an inout function, since it's implicitly convertible to any modifier. It's references or contained references which make a difference. I was making a statement about the overload resolution rules. Those also apply to reference types. > > If it comes down to supporting this, choosing any inout match arbitrarily is good enough. In this exact case, yes. In general, no. -- Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email ------- You are receiving this mail because: ------- |
February 03, 2012 [Issue 7355] inout incorrectly resolved if the same type has both mutable and immutable parts | ||||
---|---|---|---|---|
| ||||
Posted in reply to timon.gehr@gmx.ch | http://d.puremagic.com/issues/show_bug.cgi?id=7355 --- Comment #13 from Steven Schveighoffer <schveiguy@yahoo.com> 2012-02-03 15:46:53 PST --- Once again you are right Timon! I was neglecting to see in the original example that the call was id(foo(y)) instead of just id(y)! I sometimes cannot penetrate your inadvertent obfuscation :) Your original example was (should have been) sufficient. I see now that inout(const) must be a consideration for substituting inout. Now, regarding overload resolution as it pertains to resolving what inout means, I still think a simple algorithm as I previously stated should work. We just need to add inout(const) as a possible substitute. I think the ordering should be something like: mutable, immutable, inout, inout(const), const Note that inout and inout(const) are the local version (not the wildcard version). Also note that mutable, immutable, and inout could be in any order. The algorithm I stated earlier needs to be modified. The easiest way to state it is, try each of the above substitutes for inout in order, and the first one that type-checks, wins. I think it works because there are no cycles in the implicit conversion graph: mutable ----+------------------+ | | immutable --+--> inout(const) -+-> const | | inout ------+------------------+ But I don't see why we need complex overload rules as you stated. Can you show a counter-example to my simple design? I'd like to keep things simple, because inout is already difficult to understand and making it simple to explain is a huge benefit. -- Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email ------- You are receiving this mail because: ------- |
February 03, 2012 [Issue 7355] inout incorrectly resolved if the same type has both mutable and immutable parts | ||||
---|---|---|---|---|
| ||||
Posted in reply to timon.gehr@gmx.ch | http://d.puremagic.com/issues/show_bug.cgi?id=7355 --- Comment #14 from Steven Schveighoffer <schveiguy@yahoo.com> 2012-02-03 15:59:19 PST --- Er... messed up that graph a smidge (In reply to comment #13) > mutable -----------------------+ > | > immutable --+--> inout(const) -+-> const > | | > inout ------+------------------+ -- Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email ------- You are receiving this mail because: ------- |
February 04, 2012 [Issue 7355] inout incorrectly resolved if the same type has both mutable and immutable parts | ||||
---|---|---|---|---|
| ||||
Posted in reply to timon.gehr@gmx.ch | http://d.puremagic.com/issues/show_bug.cgi?id=7355 --- Comment #15 from Steven Schveighoffer <schveiguy@yahoo.com> 2012-02-03 16:04:11 PST --- (In reply to comment #14) > Er... messed up that graph a smidge > > (In reply to comment #13) > > mutable -----------------------+ > > | > > immutable --+--> inout(const) -+-> const > > | | > > inout ------+------------------+ Gah! I suck at ASCII graphs, immutable converts to const too, but you get my point though :) -- Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email ------- You are receiving this mail because: ------- |
February 04, 2012 [Issue 7355] inout incorrectly resolved if the same type has both mutable and immutable parts | ||||
---|---|---|---|---|
| ||||
Posted in reply to timon.gehr@gmx.ch | http://d.puremagic.com/issues/show_bug.cgi?id=7355 --- Comment #16 from timon.gehr@gmx.ch 2012-02-03 16:26:32 PST --- (In reply to comment #13) > Once again you are right Timon! > > I was neglecting to see in the original example that the call was id(foo(y)) > instead of just id(y)! I sometimes cannot penetrate your inadvertent > obfuscation :) Your original example was (should have been) sufficient. > OK. What measures can I take to less obfuscate my code? =) > I see now that inout(const) must be a consideration for substituting inout. > > Now, regarding overload resolution as it pertains to resolving what inout means, I still think a simple algorithm as I previously stated should work. We just need to add inout(const) as a possible substitute. I think the ordering should be something like: > > mutable, immutable, inout, inout(const), const > > Note that inout and inout(const) are the local version (not the wildcard > version). Also note that mutable, immutable, and inout could be in any order. > > The algorithm I stated earlier needs to be modified. The easiest way to state it is, try each of the above substitutes for inout in order, and the first one that type-checks, wins. I think it works because there are no cycles in the implicit conversion graph: > > mutable -----------------------+ > | > immutable --+--> inout(const) -+-> const > | | > inout ------+------------------+ > > But I don't see why we need complex overload rules as you stated. Can you show a counter-example to my simple design? I think what you propose would work. But the overload rule I want to add (see issue 7431) is actually quite intuitive and I think it is a good move to make the overload rules consistent enough so that we could re-use them for inout matching. It benefits code that does not use inout too. Not fixing the overload rules would result in inout being _more_ powerful than three overloads => inout could not be replaced by three overloads transparently, if it was later determined that the different const versions need different function bodies. immutable(int)[] foo(immutable(int)[] x,float){return x;} const(int)[] foo(const(int)[] x, float){return x;} int[] foo(int[] x,float){return x;} inout(int)[] bar(inout(int)[] x,float){return x;} void main(){ foo([1,2,3],4); // would not work bar([1,2,3],4); // would work! } > I'd like to keep things simple, because > inout is already difficult to understand and making it simple to explain is a > huge benefit. I think 'create pseudo-overloads and use the rules for overloading' is simplest (with the additional overloading rule, so that it works), but what you propose should work too. The only difference between using the repaired overload resolution and your proposal I can see is that yours introduces possible disambiguation in the case of multiple alias this. I don't know if this is good or bad (my initial proposal had the same characteristic). inout(int)[] foo(inout(int)[] x){return x;} class C{ int[] x(){writeln("x!");return new int[10];} immutable int[] y(){writeln("y!");return new immutable(int)[10];} alias x this; alias y this; } void main(){ C c = new C; foo(c); // should this work or fail? } -- Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email ------- You are receiving this mail because: ------- |
February 04, 2012 [Issue 7355] inout incorrectly resolved if the same type has both mutable and immutable parts | ||||
---|---|---|---|---|
| ||||
Posted in reply to timon.gehr@gmx.ch | http://d.puremagic.com/issues/show_bug.cgi?id=7355 --- Comment #17 from Steven Schveighoffer <schveiguy@yahoo.com> 2012-02-03 16:54:12 PST --- (In reply to comment #16) > (In reply to comment #13) > > Once again you are right Timon! > > > > I was neglecting to see in the original example that the call was id(foo(y)) > > instead of just id(y)! I sometimes cannot penetrate your inadvertent > > obfuscation :) Your original example was (should have been) sufficient. > > > > OK. What measures can I take to less obfuscate my code? =) hehe, use temporaries to demonstrate what types should be. Or more inline comments maybe. I just missed seeing the double call in one expression. For example, this could have been more effective: // your definition for foo inout(int)[] id(inout(int)[] x) { return x;} inout(const(int))[] bar(inout(int)[] x) { inout(const(int))[] tmp = foo(x); return id(tmp); // need to be able to call } > I think what you propose would work. > > But the overload rule I want to add (see issue 7431) is actually quite intuitive and I think it is a good move to make the overload rules consistent enough so that we could re-use them for inout matching. It benefits code that does not use inout too. > > Not fixing the overload rules would result in inout being _more_ powerful than three overloads => inout could not be replaced by three overloads transparently, if it was later determined that the different const versions need different function bodies. inout is *already* more powerful. It guarantees no molestation, even for mutable args. But I see your point. I'm not opposed to fixing both, but this way of explaining inout is simple to me, and to someone who doesn't want to get into the complexities of understanding overload resolution. In other words, one doesn't have to be able to understand overload resolution to understand inout. Consequently, if the way it gets implemented is that overload resolution is fixed, and then inout uses that, it's not any different, but it's easier to explain this way (IMO). > The only difference between using the repaired overload > resolution and your proposal I can see is that yours introduces possible > disambiguation in the case of multiple alias this. I don't know if this is good > or bad (my initial proposal had the same characteristic). > > inout(int)[] foo(inout(int)[] x){return x;} > > class C{ > int[] x(){writeln("x!");return new int[10];} > immutable int[] y(){writeln("y!");return new immutable(int)[10];} > alias x this; > alias y this; > } > > void main(){ > C c = new C; > foo(c); // should this work or fail? > } I think you give me too many headaches :) My gut says this should fail, because the call is not just ambiguously typed, but what you *pass* to the call is ambiguous. Consider this less benign example: struct S { int[] x; immutable(int)[] y; alias x this; alias y this; } x and y are not just generated temporaries, so the data you pass could be different depending on the compiler choice of what order to try the first three type constructors. My rules depend on the assumption that the argument type is already decided. In the case of literals, that's ok, because an arbitrary choice doesn't change code paths, just the type of the expression. In this case, we have to cry ambiguity, and fail to compile, in the name of consistency. So how to amend my algorithm? I suppose something like: try at a minimum immutable, mutable, and inout. If more than one of these typechecks, the call is ambiguous. -- Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email ------- You are receiving this mail because: ------- |
February 04, 2012 [Issue 7355] inout incorrectly resolved if the same type has both mutable and immutable parts | ||||
---|---|---|---|---|
| ||||
Posted in reply to timon.gehr@gmx.ch | http://d.puremagic.com/issues/show_bug.cgi?id=7355 --- Comment #18 from timon.gehr@gmx.ch 2012-02-03 17:14:50 PST --- (In reply to comment #17) > (In reply to comment #16) > > (In reply to comment #13) > > > Once again you are right Timon! > > > > > > I was neglecting to see in the original example that the call was id(foo(y)) > > > instead of just id(y)! I sometimes cannot penetrate your inadvertent > > > obfuscation :) Your original example was (should have been) sufficient. > > > > > > > OK. What measures can I take to less obfuscate my code? =) > > hehe, use temporaries to demonstrate what types should be. Or more inline comments maybe. I just missed seeing the double call in one expression. > > For example, this could have been more effective: > > // your definition for foo > > inout(int)[] id(inout(int)[] x) { return x;} > > inout(const(int))[] bar(inout(int)[] x) > { > inout(const(int))[] tmp = foo(x); > return id(tmp); // need to be able to call > } > > > I think what you propose would work. > > > > But the overload rule I want to add (see issue 7431) is actually quite intuitive and I think it is a good move to make the overload rules consistent enough so that we could re-use them for inout matching. It benefits code that does not use inout too. > > > > Not fixing the overload rules would result in inout being _more_ powerful than three overloads => inout could not be replaced by three overloads transparently, if it was later determined that the different const versions need different function bodies. > > inout is *already* more powerful. It guarantees no molestation, even for mutable args. > Indeed, it gives the same guarantees as const. > But I see your point. I'm not opposed to fixing both, but this way of explaining inout is simple to me, and to someone who doesn't want to get into the complexities of understanding overload resolution. In other words, one doesn't have to be able to understand overload resolution to understand inout. > One goal of overload resolution is to be as intuitive as possible. Few programmers who use overloading are actually intimately familiar with the rules that govern the resolution process. > Consequently, if the way it gets implemented is that overload resolution is fixed, and then inout uses that, it's not any different, but it's easier to explain this way (IMO). > That is probably true. > > The only difference between using the repaired overload > > resolution and your proposal I can see is that yours introduces possible > > disambiguation in the case of multiple alias this. I don't know if this is good > > or bad (my initial proposal had the same characteristic). > > > > inout(int)[] foo(inout(int)[] x){return x;} > > > > class C{ > > int[] x(){writeln("x!");return new int[10];} > > immutable int[] y(){writeln("y!");return new immutable(int)[10];} > > alias x this; > > alias y this; > > } > > > > void main(){ > > C c = new C; > > foo(c); // should this work or fail? > > } > > I think you give me too many headaches :) My gut says this should fail, because the call is not just ambiguously typed, but what you *pass* to the call is ambiguous. Consider this less benign example: > > struct S > { > int[] x; > immutable(int)[] y; > alias x this; > alias y this; > } > > x and y are not just generated temporaries, so the data you pass could be different depending on the compiler choice of what order to try the first three type constructors. > > My rules depend on the assumption that the argument type is already decided. In the case of literals, that's ok, because an arbitrary choice doesn't change code paths, just the type of the expression. > > In this case, we have to cry ambiguity, and fail to compile, in the name of consistency. So how to amend my algorithm? I suppose something like: > > try at a minimum immutable, mutable, and inout. If more than one of these typechecks, the call is ambiguous. What if one is a better match than the other? If it should fail I think we should use the repaired overload resolution. It has exactly the required semantics. -- Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email ------- You are receiving this mail because: ------- |
February 04, 2012 [Issue 7355] inout incorrectly resolved if the same type has both mutable and immutable parts | ||||
---|---|---|---|---|
| ||||
Posted in reply to timon.gehr@gmx.ch | http://d.puremagic.com/issues/show_bug.cgi?id=7355 --- Comment #19 from Steven Schveighoffer <schveiguy@yahoo.com> 2012-02-03 17:18:54 PST --- (In reply to comment #18) > (In reply to comment #17) > > try at a minimum immutable, mutable, and inout. If more than one of these typechecks, the call is ambiguous. > > What if one is a better match than the other? If it should fail I think we should use the repaired overload resolution. It has exactly the required semantics. Example? How can immutable match "better" than mutable? I guess it would depend on the definition of "better". -- Configure issuemail: http://d.puremagic.com/issues/userprefs.cgi?tab=email ------- You are receiving this mail because: ------- |
Copyright © 1999-2021 by the D Language Foundation