February 21, 2005
On Mon, 21 Feb 2005 14:04:00 +0100, xs0 <xs0@xs0.com> wrote:
>>     double cost=sqrt(..);
>
> should of course be
>
> double cost=mysqrt(..);
>
> :)

I really should read all threads before answering.. regardless that wont compile.

Regan
February 21, 2005
On Mon, 21 Feb 2005 14:03:04 +0100, xs0 wrote:

>> The main one seems to be, what would happen if someone coded a function call but did not assign the return to anything? How would the compiler know which signature to use if it had to use return-type as a determinate?
> 
> Well, that's not the only "main" issue, imho.. For example, consider:
> 
> int mysqrt(double a) {
>     return floor(sqrt(a));
> }
> 
> double eval_some_cost(...)
> {
>      double cost=sqrt(..);
> 
>      if (something)
>          cost*=1.3;
> 
>      return cost;
> }
> 
> Now, this works fine and all. But, I (or my co-worker) can decide sometime in the future that there should also be a double mysqrt(double) returning the unrounded square root (or even something completely different, but that is bad style, I guess). The problem is that without any change in either the int version or in eval_some_cost(), the code will produce different results. This is most probably the reason why ambiguous function calls are not allowed and you need to be explicit which version you want to use.

I understand what you are saying. However, I imagine it would work in a similar manner to function signature matching does today. That is it would have to exactly match or an error is produced. Consider what it currently does...

  void foo(real x) {}

  void main() { foo(5); }

This compiles okay as the compiler silently (implictly) converts the
integer 5 to a real 5 before calling the foo routine. But, to paraphrase a
collegue "But, I (or my co-worker) can decide sometime in the future that
there should also be an foo(int)", such as ...

  void foo(real x) {}
  void foo(int x) {}

  void main() { foo(5); }

Now the compiler complains because of the ambiguity.

So if return type was included in the deal, your example would also have the compiler complaining when the new double mysqrt() was included into the mix.

> 
>> For example...
>>    cast(int)foo('x');  // Call the 'int' version and ignore the result.
>>    bar( cast(real)foo('y') ); // Call the 'real' version of foo and bar.
> 
> Hmm, but there's no casting, so why use the cast() operator? I'd like better something along the lines of

Because it was just an example! I'm not seriously saying *this must be the syntax*. But I did note that you understood what I was getting at ;-)

> foo:int('x');
> bar(foo:real('y'));

Maybe ...
   foo('x'):int;
   bar( foo('y'):real; );

not that it matters too much (because Walter will *never* implement it anyway - too much against his world-view, I suspect).

> And cast(type)obj can almost be directly translated to obj.opCast:type() (almost because implicit casts should still be allowed when there's no ambiguity). Ambiguity should be defined differently for return types, though - the above problem is still not solved, even if there is an exact match (actually, precisely because of the exact match).

Sorry, but I don't follow you here. What do you mean by "the above problem is still not solved, ..."? If the coder explicitly tells the compiler which version of the function to call, what is the problem?

> So, I'd say that if there are return types A and B, and either can be implicitly casted to the other, the caller should be forced to specify which is the right one when using either.

Yes, exactly.

> That's ok, though; even if you write A a=func(), it actually doesn't mean that func() should return something of type A, it just means that you'd like the compiler to implicitly cast it to A.

If it is possible, but this is a different thing to getting the compiler to sort out which of the possible function signatures to select. Which is what I'm talking about.

> This logic is actually the same as with double a=2/3 -- you'll still get 0, because the casting gets done on assignment, not evaluation..

True, but what has this got to do with function selection? I'm missing the point I think, sorry.

-- 
Derek
Melbourne, Australia
February 22, 2005
> Can you give an example which compile, so I can mess with it :)

The point wasn't to compile it :) If you really want to, you can probably just return (int)sqrt(..) or something..


>> The problem is that without any change in either the int version  
>> or in eval_some_cost(), the code will produce different
>> results. This is  most probably the reason why ambiguous function calls are not allowed  and you need to be explicit which version you want to use.
> 
> 
> Consider this, existing, similar problem:
> 
> class Parent {
>     void foo(long a) { printf("parent: foo: long\n"); }
> }
> 
> class Child : Parent {
>     //uncomment this line
>     //void foo(int a) { printf("child: foo: int\n"); }
> }
> 
> void main() {
>     Child c = new Child();
>     long a = 5;
>     c.foo(a);
> }

Yup, it is similar, but I think that in this case, Parent.foo will still be called (because long can't be implicitly cast to int?). And even if you switched int and long, it's not the quite the same - Child is a new class with a new contract and whatnot.. But yes, in any case, overloading methods with implicitly castable types (both the return type and the parameters) is asking for trouble. Still, overloading parameter types is an issue that's present in other languages as well (like C++ and Java), so I guess people (i.e. me) are more used to it, while overloading on return type is far less common..


>> Hmm, but there's no casting, so why use the cast() operator?
> 
> Because it has basically the same meaning.

How does it have the same meaning? Normally, you cast() something to make it one type from another type. If you select some specific return type, you'd already get that type as the result, so you wouldn't want to cast it. I'd certainly read cast(int)func(params) as func _not_ returning int.


> Agreed, but it's not the way D works currently.

Yup, currently you can't overload just on return types :)


> This whole issue comes down to where you draw the line, where you balance  convenience vs pedanticism. (if that aint a word it should be). Walter has  drawn it in one place, almost everyone disagrees in some way, large or  small.

I certainly don't disagree with Walter; actually I think D is the best language I've ever seen (at least in the compiled-to-native-code category).

Well, considering how opCast is the only problem where not having return-type-overloads is really an issue, perhaps it could just get special treatment and the problem'd be solved?

class A {
   cast(T : int) { return ...; }
   cast(T : double) { return ...; }
}

or something.. looks obvious to me :) (T is just dummy, of course, but the syntax indicates specialization of cast for different types, much like with templates)


xs0
February 22, 2005
On Tue, 22 Feb 2005 01:26:44 +0100, xs0 <xs0@xs0.com> wrote:
>> Can you give an example which compile, so I can mess with it :)
>
> The point wasn't to compile it :) If you really want to, you can probably just return (int)sqrt(..) or something..

If it doesn't compile and you're trying to show something that 'could' happen then it's an invalid example.

>>> The problem is that without any change in either the int version
>  >> or in eval_some_cost(), the code will produce different
>>> results. This is  most probably the reason why ambiguous function calls are not allowed  and you need to be explicit which version you want to use.
>>   Consider this, existing, similar problem:
>>  class Parent {
>>     void foo(long a) { printf("parent: foo: long\n"); }
>> }
>>  class Child : Parent {
>>     //uncomment this line
>>     //void foo(int a) { printf("child: foo: int\n"); }
>> }
>>  void main() {
>>     Child c = new Child();
>>     long a = 5;
>>     c.foo(a);
>> }
>
> Yup, it is similar, but I think that in this case, Parent.foo will still be called (because long can't be implicitly cast to int?).

Wrong.

> And even if you switched int and long, it's not the quite the same - Child is a new class with a new contract and whatnot.. But yes, in any case, overloading methods with implicitly castable types (both the return type and the parameters) is asking for trouble. Still, overloading parameter types is an issue that's present in other languages as well (like C++ and Java), so I guess people (i.e. me) are more used to it, while overloading on return type is far less common..
>
>
>>> Hmm, but there's no casting, so why use the cast() operator?
>>  Because it has basically the same meaning.
>
> How does it have the same meaning? Normally, you cast() something to make it one type from another type. If you select some specific return type, you'd already get that type as the result, so you wouldn't want to cast it. I'd certainly read cast(int)func(params) as func _not_ returning int.

It has the same meaning as casting a parameter to force the compiler to select the correct overload. Sure, casting a parameter does actually cast the parameter, but so does casting a return value. The only difference is the order it happens in.

>> Agreed, but it's not the way D works currently.
>
> Yup, currently you can't overload just on return types :)

That isn't what I meant.

>> This whole issue comes down to where you draw the line, where you balance  convenience vs pedanticism. (if that aint a word it should be). Walter has  drawn it in one place, almost everyone disagrees in some way, large or  small.
>
> I certainly don't disagree with Walter; actually I think D is the best language I've ever seen (at least in the compiled-to-native-code category).
>
> Well, considering how opCast is the only problem where not having return-type-overloads is really an issue, perhaps it could just get special treatment and the problem'd be solved?

Perhaps. A more generic soln would be more useful however.

> class A {
>     cast(T : int) { return ...; }
>     cast(T : double) { return ...; }
> }
>
> or something.. looks obvious to me :) (T is just dummy, of course, but the syntax indicates specialization of cast for different types, much like with templates)

This is a hack. I think we can do better.

Regan
February 22, 2005
> Now the compiler complains because of the ambiguity. 

It does? That's great, actually. My guess, however, is that it only does so because a constant was used. If it was a variable, it wouldn't (or maybe I'm forgetting something?)


> Maybe ...
>    foo('x'):int;
>    bar( foo('y'):real; );
> 
> not that it matters too much (because Walter will *never* implement it
> anyway - too much against his world-view, I suspect).

Well, I agree it's best not to implement it (see my other post), but to explain my syntax - my first thought was foo.int('x'), which somehow makes sense to me, but there's two problems - int has to be looked up to determine whether it is a type (which is not a problem with basic types, but is with everything else), and furthermore, if you have a type within a type, you can't be sure what is what. is a.b.c "a returning b.c" or "a.b returning c"? so, I just replaced the dot with : to avoid both problems (and, it has a similar meaning as in template(T : Q), which is specialization)



> Sorry, but I don't follow you here. What do you mean by "the above problem
> is still not solved, ..."? If the coder explicitly tells the compiler which
> version of the function to call, what is the problem?

If the coder does that, there is no problem. The "above problem" is that perhaps he didn't at some point in time, because there was nothing to specify (i.e. there was only one method). Later, another method was added, and because the return type exactly matched the inferred return type (_double_ a = mysqrt(..)), another method was chosen and it was the wrong one.


> If it is possible, but this is a different thing to getting the compiler to
> sort out which of the possible function signatures to select. Which is what
> I'm talking about.

Well, you said "To counter this, one could make the rule that every call to a function must either assign the result or [snip]". I think that just assigning the result is not indicative enough..


>> [snip: double a=2/3]
> True, but what has this got to do with function selection? I'm missing the
> point I think, sorry.

The point was that the type you're assigning the result to doesn't have any influence on how that result is produced, which is good - you don't want 2/3 to mean 0 sometimes and 0.66666 other times. The same goes for methods (i.e. you don't want another method to be chosen, just because the result will be assigned to some other type).


xs0
February 22, 2005
> If it doesn't compile and you're trying to show something that 'could'  happen then it's an invalid example.

Well, I didn't specify which floor() and sqrt() get called, so one could argue that the example was valid? :)


>> Yup, it is similar, but I think that in this case, Parent.foo will still  be called (because long can't be implicitly cast to int?).
> 
> Wrong.

My mistake then.. Although I think it should be the case, according to
http://www.digitalmars.com/d/type.html
(which doesn't list long in integer promotions, true, but I was extrapolating the int case).


> It has the same meaning as casting a parameter to force the compiler to  select the correct overload. Sure, casting a parameter does actually cast  the parameter, but so does casting a return value. The only difference is  the order it happens in.

Well, you make it sound as if you're trying to work around a bug in the compiler (which would be its complaining), but you're really just producing an exact match. Casting a return value, otoh, happens _after_ the function returns, so it can't be used for the same purpose...

And consider the case where you'd want the double version, but you'd like to also cast it to int:

cast(int)(cast(double)func(3))

?


>> Yup, currently you can't overload just on return types :)
> 
> That isn't what I meant.

So what did you mean?


>> Well, considering how opCast is the only problem where not having  return-type-overloads is really an issue, perhaps it could just get  special treatment and the problem'd be solved?
> 
> Perhaps. A more generic soln would be more useful however.

Well, it depends on what you consider useful, I guess. I find it rather useful if the language doesn't contain features which complicate things without any real benefit.. I mean, what's the real difference between cast(int)something and something.asInt()? The only reasonable case I can think of are templates, so you can use the same code both for classes that are something, and classes than can cast themselves as that same something (like a List or whatever). And in that respect, having multiple possible casts might be a good idea..


> This is a hack. I think we can do better.

Well, what is your better suggestion then? Your suggestion (cast(type)func) is imho much more of a hack. BTW, I wasn't suggesting a dummy parameter, which you already proclaimed a hack, merely a dummy type alias, which could possibly even be used with a mixin or something, so it wouldn't even be a dummy anymore. As in:

cast(T: int)    { return Something!(T).convert(this); }
cast(T: double) { return Something!(T).convert(this); }


xs0
February 22, 2005
On Tue, 22 Feb 2005 02:12:27 +0100, xs0 <xs0@xs0.com> wrote:
>> If it doesn't compile and you're trying to show something that 'could'  happen then it's an invalid example.
>
> Well, I didn't specify which floor() and sqrt() get called, so one could argue that the example was valid? :)

If you had specified them, would it have compiled?
If so, post the example here, if not...

>>> Yup, it is similar, but I think that in this case, Parent.foo will still  be called (because long can't be implicitly cast to int?).
>>  Wrong.
>
> My mistake then.. Although I think it should be the case, according to
> http://www.digitalmars.com/d/type.html
> (which doesn't list long in integer promotions, true, but I was extrapolating the int case).

It doesn't say exactly what it does on that page, it probably should.

I notice you don't try examples which are posted, I think you should, before commenting on them, it's a waste of time guessing what's going to happen.

>> It has the same meaning as casting a parameter to force the compiler to  select the correct overload. Sure, casting a parameter does actually cast  the parameter, but so does casting a return value. The only difference is  the order it happens in.
>
> Well, you make it sound as if you're trying to work around a bug in the compiler (which would be its complaining), but you're really just producing an exact match.

True, however I'm also specifying the overload I want, because the compiler cannot read my mind and I doesn't guess (thank Bob).

> Casting a return value, otoh, happens _after_ the function returns, so it can't be used for the same purpose...

I disagree. As I said, I'm explicitly specifying the overload I mean.

> And consider the case where you'd want the double version, but you'd like to also cast it to int:
>
> cast(int)(cast(double)func(3))

So you're saying you have:

int func(int a) {}
double func(int a) {}

void main() {
  int a;
  a = func();
}

and you want to call the double version, right?
If so, then yes, your syntax looks fine.

>>> Yup, currently you can't overload just on return types :)
>>  That isn't what I meant.
>
> So what did you mean?

Your original comment was "So, I'd say that if there are return types A and B, and either can be implicitly casted to the other, the caller should be forced to specify which is the right one when using either."

I replied: "Agreed, but it's not the way D works currently."

Turns out, I was wrong, D does work this way currently.

Your comment: "Yup, currently you can't overload just on return types :)"

has no bearing on this however, and wasn't what I meant, so I said that.

>>> Well, considering how opCast is the only problem where not having  return-type-overloads is really an issue, perhaps it could just get  special treatment and the problem'd be solved?
>>  Perhaps. A more generic soln would be more useful however.
>
> Well, it depends on what you consider useful, I guess. I find it rather useful if the language doesn't contain features which complicate things without any real benefit.. I mean, what's the real difference between cast(int)something and something.asInt()?

As I said above, when casting to cause an exact match I am explicitly specifying the overload I want to use, because the compiler cannot read my mind and doesn't guess.

The other is converting to an int, sure, the cast does this, but, when used to select an overload the intent is different, the fact that it's converting (which it might not be, consider int->long) is secondary.

> The only reasonable case I can think of are templates, so you can use the same code both for classes that are something, and classes than can cast themselves as that same something (like a List or whatever). And in that respect, having multiple possible casts might be a good idea..

Classes can be implicitly cast to their parent types, I believe.
Is this what you're referring to? eg.

class A {}
class B : A {}

void foo(A a) {}

void main() {
  B b = new B();
  foo(b);
}

no cast is required, even if you add...

class A {}
class B : A {}

void foo(A a) { printf("A"); }
void foo(B b) { printf("B"); }

void main() {
  B b = new B();
  A a = new A();
  foo(a);
  foo(b);
}

>> This is a hack. I think we can do better.
>
> Well, what is your better suggestion then?

cast(type)

> Your suggestion (cast(type)func) is imho much more of a hack.

You're welcome to think so. In the end Walter decides anyway.

> BTW, I wasn't suggesting a dummy parameter, which you already proclaimed a hack, merely a dummy type alias, which could possibly even be used with a mixin or something, so it wouldn't even be a dummy anymore. As in:
>
> cast(T: int)    { return Something!(T).convert(this); }
> cast(T: double) { return Something!(T).convert(this); }

That syntax does not seem obvious to me.
Which is why I prefer cast(type), it has an obvious commonly understood meaning.

Regan
February 22, 2005

Regan Heath wrote:
> On Tue, 22 Feb 2005 01:26:44 +0100, xs0 <xs0@xs0.com> wrote:
> 
>>> This whole issue comes down to where you draw the line, where you  balance  convenience vs pedanticism. (if that aint a word it should  be). Walter has  drawn it in one place, almost everyone disagrees in  some way, large or  small.

True. But then we might not always be able to see the real reason.
After all, his CV looks better than ours! Also, some of the reasons
might take a lot of explaining, especially if they're subtle. All
that is time away from actually writing the D compiler.   .-)
And sometimes it may be just as simple as a feature demanding more
research and writing from Walter, than what it's worth.

>> I certainly don't disagree with Walter; actually I think D is the best  language I've ever seen (at least in the compiled-to-native-code  category).

Me too! And an ever increasing number of my friends here!

>> Well, considering how opCast is the only problem where not having  return-type-overloads is really an issue, perhaps it could just get  special treatment and the problem'd be solved?

FTR: I still have no opinion for or against overloading on return
type.

Can we find examples where not having it produces cumbersome code
compared with having RTO?
February 22, 2005

Regan Heath wrote:
> In other words, implicit conversion causes the problem, and it's a  seperate issue to solve, adding this new explicit function selection will  not add new sources of bugs, they're already there.

This reveals a useful use for overloading on return type.

One could explicitly disallow certain cases:

real foo(sometype z) {whatever}
int  foo(sometype z) {assert(0)}

real x;
x = foo(bla);  // ok
int y;
y = foo(bla);  // Assertion failed.

The overloading mechanism should of course first search the
overloads extensively, before any implicit conversions.

Actually, we might even decide that for any function that
has return type overload, _no implicit conversions are
allowed_.
February 22, 2005
>> Well, I didn't specify which floor() and sqrt() get called, so one could  argue that the example was valid? :)
> 
> If you had specified them, would it have compiled?
> If so, post the example here, if not...

What kind of questions is that? Are you suggesting you're not sure whether "return floor(sqrt(a))" can ever be a valid function body? And I can't produce a compilable example, because the example included an overload on return type, which is not possible...


> I notice you don't try examples which are posted, I think you should,  before commenting on them, it's a waste of time guessing what's going to  happen.

Like I said, I can't produce a compilable example (which I thought would be somewhat obvious from the nature of this thread, but I guess it isn't).


> True, however I'm also specifying the overload I want, because the  compiler cannot read my mind and I doesn't guess (thank Bob).

Maybe, but you're specifying the overload you want merely as a side-effect of casting parameters.


>> Casting a return value, otoh, happens _after_ the function returns, so  it can't be used for the same purpose...
> 
> I disagree. As I said, I'm explicitly specifying the overload I mean.

No, you're not.. You're just casting the return type. You can cast the return type now, so casting obviously doesn't mean you're trying to select an overload..


> So you're saying you have:
> 
> int func(int a) {}
> double func(int a) {}
> 
> void main() {
>   int a;
>   a = func();
> }
> 
> and you want to call the double version, right?
> If so, then yes, your syntax looks fine.

Since you were so smart about compilable examples, I'm surprised you wrote an example that can't be compiled :P (Sorry, couldn't resist)

I think you're not clear on how evaluation of expressions works, and that is the reason you think using cast for this would be fine. Let me try to explain.

Let's focus just on ints and doubles. When you type cast(int)expression it's basically the same as if you called some compiler-provided function named, for example, cast_to_int(), so the above translates to cast_to_int(expression). That function has several overloads, in this case two (because I limited the example to two types):

int cast_to_int(int value) {
   return value;
}
int cast_to_int(double value) {
   return ...; // the largest integer not less than value
}

Same goes for cast(double). Now, when you type

f(cast(int)a, cast(double)b)

it gets translated to

f(cast_to_int(a), cast_to_double(b))

and NOT to

f(a, b); // OY, COMPILER, I'M TALKING TO YOU! I WANT f(int,double)

So, before the func() gets the parameters, they're already cast to the right types. Incidentally, that makes an exact match with one of the overloads of func(), so the compiler knows which one you meant.

OTOH, if you write cast(int)func(a,b), it gets translated to

cast_to_int(func(a,b))

From that it's obvious that casting is done _after_ the function is evaluated, so casting can't have an effect on which function is selected. Got it? (now don't start arguing I made this up; this is how it actually works (I wrote a parser or two in my life, so I know this))


> As I said above, when casting to cause an exact match I am explicitly  specifying the overload I want to use, because the compiler cannot read my  mind and doesn't guess.

No, you're not specifying the overload, you're just casting the arguments, possibly producing an exact overload match..


> The other is converting to an int, sure, the cast does this, but, when  used to select an overload the intent is different, the fact that it's  converting (which it might not be, consider int->long) is secondary.

no, it isn't secondary, just the opposite (and casting int to long is converting, the first is 32 bits, the other is 64 bits). the only cast that doesn't do anything is actually casting a type to the same type.. (even if two types are the same length and casting doesn't change one bit, it will change the semantics of expressions; for example, if you cast int 3 to uint 3, you'll get exactly the memory contents; however, if you add 3000000000, you'll get a negative value in the first case and a positive value in the second case)


>> The only reasonable case I can think of are templates, so you can use  the same code both for classes that are something, and classes than can  cast themselves as that same something (like a List or whatever). And in  that respect, having multiple possible casts might be a good idea..
> 
> 
> Classes can be implicitly cast to their parent types, I believe.
> Is this what you're referring to? eg.

Nope, I'm referring to something like this:

class SomeClass(T) {
   void doSomething(T param) {
       List a=cast(List)param;

       // now do something with a
   }
}

This template will currently work for both Ts that are themselves Lists, and Ts that have opCast() that returns a List. But, opCast() can't be overloaded, and if you want it for something else, you have a problem..


>> Well, what is your better suggestion then?
> 
> cast(type)

That's impossible (really, cast is not overload-selection-operator, it's casting-operator). Any other suggestions?


>> cast(T: int)    { return Something!(T).convert(this); }
>> cast(T: double) { return Something!(T).convert(this); }
> 
> That syntax does not seem obvious to me.

Why not? How would you suggest overloading opCast() if return-type overloading is not allowed (which is the case this suggestion is trying to cover, and I was just trying to show how T may not always be dummy)?


> Which is why I prefer cast(type), it has an obvious commonly understood  meaning.

Yes, I agree completely, it's just not what you understand it to be...


xs0