June 07, 2011
Walter Bright Wrote:

> On 6/7/2011 9:01 AM, foobar wrote:
> > Also ML has only two types: integers and floating point.
> 
> That changes everything. C has 11 integer types and 3 floating point types, which makes it ugly (and surprisingly buggy) to be forced to explicitly cast when doing mixed type expressions.

Personally I think that all those types are redundant but regardless I don't think that int -> long is the same as int -> double.
you can leave the size promotion rules untouched (implicit) since it's equivalent to upcasting. integral -> floating can however cause loss of precision and hence should be IMO explicit.
E.g. in pseudo D:

auto add (a, b) { return a + b; }
int a = ...;
long b = ...;
auto c = add (a, b);
// type for both parameters and return type is long
double d = ...;
auto e = add (a, d); // IMO, compile time error


June 07, 2011
Andrei Alexandrescu wrote:
> On 6/7/11 12:20 PM, Timon Gehr wrote:
>>
>> Jonathan M Davis wrote:
>>> ...
>>> And while D might arguably allow too many implicit conversions, it allows
>>> fewer than C or C++. I actually would expect that bugs due to implicit
>>> conversions would be fairly rare in D. And requiring more conversions to be
>>> explicit might actually make things worse, because it would become more
>>> frequently necessary to use casts, which tend to hide various types of bugs.
>>> So, while it might be better to require casting in a few more places than D
>>> currently does, on the whole it works quite well. And certainly expecting a
>>> major paradigm shift at this point is unrealistic. Minor improvements may be
>>> added to the language, and perhaps major backwards-compatible features may be
>>> added, but for the most part, D is currently in the mode of stabilizing and
>>> completing the implementation of its existing feature set. It's _far_ too late
>>> in the game to introduce something like Hindley-Milner type inference,
>>> regardless of whether it would have been a good idea in the beginning (and
>>> honestly, given D's C and C++ roots, I very much doubt that it ever would have
>>> been a good idea to have Hindley-Milner type inference in it - that would have
>>> made for a _very_ different sort of language, which definitely wouldn't be D).
>>>
>>> - Jonathan M Davis
>>
>>
>> Widening implicit casts are very convenient.
>> What I think is annoying is that D allows implicit conversions that are narrowing.
>> Eg:
>> int ->  float
>> long ->  double
>
> I'm a bit weary about these too.
>
>> real ->  double
>> double ->  float
>>
>> Especially questionable is the real ->  double ->  float chain. This and implicitly casting an integer value to a floating point type whose mantissa is too small should imho be disallowed.
>
> I used to think the same but Walter convinced me otherwise. Automatic floating point conversions allow the compiler and libraries to store intermediate results at the optimal precision without impacting user code. [snip.]

Hm ok.
But @compiler: The compiler could just as well insert explicit casts (I think
thats how it deals with implicit casts during semantic analysis anyways.), or am I
missing something?
@Libraries: True, the libraries internal precision does not impact user code. But
if the user stores intermediate results in a type that is too coarse, user code
might inadvertently impact library code in a crippling way. This then reflects
badly on user code. (float a=LibraryFunctionTakingAndReturningReal(123.2); float
c=Ditto(a); // now c is less precise than the library could offer)


Timon
June 08, 2011
On 6/7/11, foobar <foo@bar.com> wrote:
> foo(a, b) { return a + b; } // will be lowered to:
> T foo(T) (T a, T b) { return a + b; }

Thanks to someone who was trying to be clever, the win32 bindings have these templates:

template max(T) {
    T max(T a, T b)
    {
        return a > b ? a : b;
    }
}

template min(T) {
    T min(T a, T b)
    {
        return a < b ? a : b;
    }
}

They clash with std.algorithm : min and max, but at the same time they can't really be instantiated in the same way. Now I have the situation where they conflict but at the same time only the std.algorithm templates can be instantiated. Compiler errors everywhere, great.

So now I have to force my code to use std.algorithm by importing via: import std.algorithm : min, max;

or alternatively:
import std.algorithm;
alias std.algorithm.min min;
alias std.algorithm.max max;

Pain in the ass.
June 08, 2011
== Quote from Andrej Mitrovic (andrej.mitrovich@gmail.com)'s article
> ...
> So now I have to force my code to use std.algorithm by importing via:
> import std.algorithm : min, max;
> or alternatively:
> import std.algorithm;
> alias std.algorithm.min min;
> alias std.algorithm.max max;
> Pain in the ass.

You can say that again... I have the same issue with lots of other things being declared everywhere, like read, write, Stream/File (<-- very annoying, since they're both in std.file and std.stream), indexOf, toString, and lots of other things I can't remember right now.

Also, I'm not sure if this bug has been fixed in DMD 2.53, but in older versions if you said something like:

	module foo1;
	import bar: baz;

and then in another file said:

	module foo2;
	import foo1;
	import bar;

and then used baz() in foo2, it would detect a collision, when in fact they were the same thing. It wouldn't
happen if baz() wasn't explicitly declared. It was really annoying to track down.
June 08, 2011
On 6/7/11 9:50 PM, Andrei Alexandrescu wrote:
> On 6/7/11 5:35 AM, Ary Manzana wrote:
>> Well, in Ruby every parameter type is auto. And it works pretty well. :-)
>>
>> When you invoke a method and something goes wrong, like the auto
>> parameter doesn't have a method, it gives a sensible error message.
>
> It does, just at run time. The way dynamic languages and static
> languages set up computation is very different and as such difficult to
> compare something as core as function call resolution as equals for equals.
>
>> So:
>>
>> void foo(auto parameter) {
>> return parameter * 2;
>> }
>>
>> could just be the same as
>>
>> void foo(T)(T parameter) {
>> return parameter * 2;
>> }
>>
>> just with a nicer syntax (but I understand it doesn't add much).
>
> The counterpoint is that you seldom want to write foo() as written
> above. You want to clarify what family of types it is intended for.

Why? Note that in Ruby you *NEVER* say the family of types, and that is a huge bennefit. Good tests and good documentation are better than restricting the time. Look at this:

======================================================
import std.stdio;

// This is Ruby :-)
auto foo(T)(T param) {
  return param * 2;
}

class MyClass {
  int value;

  this(int value) {
    this.value = value;
  }

  int opBinary(string op)(int other) if (op == "*") {
    return value * other;
  }
}

int main() {
  auto x = foo(3);
  auto y = foo(6L);
  auto z = foo(new MyClass(10));

  writefln("%s", x);
  writefln("%s", y);
  writefln("%s", z);
  return 0;
}
======================================================

This function:

auto foo(T)(T param) {
  return param * 2;
}

Could be just as well:

auto foo(auto param) {
  return param * 2;
}

Yes, in Ruby it is checked on runtime, on D it is checked on compile-time.

What's cool about "auto param"? I have written a function once and it will work for everything that can be multiplied and it will be compiled efficiently for every type that I pass that complies to the function. Why you find it useless? Why do I have to restrict the type? I can restrict the type just for the operations being performed on it and then my function is open to any param that accepts those operations.

I know, I know. This is the same as just a template, but with a different syntax. I just don't understand why you think you will almost always would like to restrict the type you pass to the function. If you do, you limit your code. If you don't, you open your code and you have to write less code.
June 08, 2011
On 2011-06-08 02:20, Ary Manzana wrote:
> On 6/7/11 9:50 PM, Andrei Alexandrescu wrote:
> > On 6/7/11 5:35 AM, Ary Manzana wrote:
> >> Well, in Ruby every parameter type is auto. And it works pretty well. :-)
> >> 
> >> When you invoke a method and something goes wrong, like the auto parameter doesn't have a method, it gives a sensible error message.
> > 
> > It does, just at run time. The way dynamic languages and static languages set up computation is very different and as such difficult to compare something as core as function call resolution as equals for equals.
> > 
> >> So:
> >> 
> >> void foo(auto parameter) {
> >> return parameter * 2;
> >> }
> >> 
> >> could just be the same as
> >> 
> >> void foo(T)(T parameter) {
> >> return parameter * 2;
> >> }
> >> 
> >> just with a nicer syntax (but I understand it doesn't add much).
> > 
> > The counterpoint is that you seldom want to write foo() as written above. You want to clarify what family of types it is intended for.
> 
> Why? Note that in Ruby you *NEVER* say the family of types, and that is a huge bennefit. Good tests and good documentation are better than restricting the time. Look at this:
> 
> ====================================================== import std.stdio;
> 
> // This is Ruby :-)
> auto foo(T)(T param) {
> return param * 2;
> }
> 
> class MyClass {
> int value;
> 
> this(int value) {
> this.value = value;
> }
> 
> int opBinary(string op)(int other) if (op == "*") {
> return value * other;
> }
> }
> 
> int main() {
> auto x = foo(3);
> auto y = foo(6L);
> auto z = foo(new MyClass(10));
> 
> writefln("%s", x);
> writefln("%s", y);
> writefln("%s", z);
> return 0;
> }
> ======================================================
> 
> This function:
> 
> auto foo(T)(T param) {
> return param * 2;
> }
> 
> Could be just as well:
> 
> auto foo(auto param) {
> return param * 2;
> }
> 
> Yes, in Ruby it is checked on runtime, on D it is checked on compile-time.
> 
> What's cool about "auto param"? I have written a function once and it will work for everything that can be multiplied and it will be compiled efficiently for every type that I pass that complies to the function. Why you find it useless? Why do I have to restrict the type? I can restrict the type just for the operations being performed on it and then my function is open to any param that accepts those operations.
> 
> I know, I know. This is the same as just a template, but with a different syntax. I just don't understand why you think you will almost always would like to restrict the type you pass to the function. If you do, you limit your code. If you don't, you open your code and you have to write less code.

You want to restrict it for several reasons, including the fact that if you don't, you get hideous error messages when you use a type with a template that it doesn't work with (templates in C++ are famous for these). The fact of the matter is that any type which a template is compiled with needs to fulfill a particular set of requirements to work with that template, and you need a way to express that. Letting the compiler choke on it just doesn't cut it. That's one of the reasons that template constraints are so great. That's why the C++ guys wanted concepts.

Another major reason though is that you sometimes need control over what types can be used with a particular function or template. Just because a type happens to have all of the operations that the function requires does not mean that it's going to work with it. You frequently either need to restrict a template to a paricular set of types or create specializations for a template where a particular type so that it works correctly with that type or is better optimized for that type (even if it would have worked with the original template). A classic example of this in Phobos is strings. Functions which operate on arrays must frequently special-case them in order to work correctly. They would compile just fine with the basic template, but the resulting code would be wrong. So, the template gets specializations for strings. Many range-based functions work fine with strings, but sometimes it's just more efficient to special-case them.

You _can_ create templates with no template constraints if you want to, but it's generally ill-advised and in a number of cases will lead to incorrect code. And once you need template constraints, then the proposed auto syntax doesn't work anymore.

- Jonathan M Davis
June 09, 2011
On 6/8/11 11:56 PM, Jonathan M Davis wrote:
> On 2011-06-08 02:20, Ary Manzana wrote:
>> On 6/7/11 9:50 PM, Andrei Alexandrescu wrote:
>>> On 6/7/11 5:35 AM, Ary Manzana wrote:
>>>> Well, in Ruby every parameter type is auto. And it works pretty well.
>>>> :-)
>>>>
>>>> When you invoke a method and something goes wrong, like the auto
>>>> parameter doesn't have a method, it gives a sensible error message.
>>>
>>> It does, just at run time. The way dynamic languages and static
>>> languages set up computation is very different and as such difficult to
>>> compare something as core as function call resolution as equals for
>>> equals.
>>>
>>>> So:
>>>>
>>>> void foo(auto parameter) {
>>>> return parameter * 2;
>>>> }
>>>>
>>>> could just be the same as
>>>>
>>>> void foo(T)(T parameter) {
>>>> return parameter * 2;
>>>> }
>>>>
>>>> just with a nicer syntax (but I understand it doesn't add much).
>>>
>>> The counterpoint is that you seldom want to write foo() as written
>>> above. You want to clarify what family of types it is intended for.
>>
>> Why? Note that in Ruby you *NEVER* say the family of types, and that is
>> a huge bennefit. Good tests and good documentation are better than
>> restricting the time. Look at this:
>>
>> ======================================================
>> import std.stdio;
>>
>> // This is Ruby :-)
>> auto foo(T)(T param) {
>> return param * 2;
>> }
>>
>> class MyClass {
>> int value;
>>
>> this(int value) {
>> this.value = value;
>> }
>>
>> int opBinary(string op)(int other) if (op == "*") {
>> return value * other;
>> }
>> }
>>
>> int main() {
>> auto x = foo(3);
>> auto y = foo(6L);
>> auto z = foo(new MyClass(10));
>>
>> writefln("%s", x);
>> writefln("%s", y);
>> writefln("%s", z);
>> return 0;
>> }
>> ======================================================
>>
>> This function:
>>
>> auto foo(T)(T param) {
>> return param * 2;
>> }
>>
>> Could be just as well:
>>
>> auto foo(auto param) {
>> return param * 2;
>> }
>>
>> Yes, in Ruby it is checked on runtime, on D it is checked on compile-time.
>>
>> What's cool about "auto param"? I have written a function once and it
>> will work for everything that can be multiplied and it will be compiled
>> efficiently for every type that I pass that complies to the function.
>> Why you find it useless? Why do I have to restrict the type? I can
>> restrict the type just for the operations being performed on it and then
>> my function is open to any param that accepts those operations.
>>
>> I know, I know. This is the same as just a template, but with a
>> different syntax. I just don't understand why you think you will almost
>> always would like to restrict the type you pass to the function. If you
>> do, you limit your code. If you don't, you open your code and you have
>> to write less code.
>
> You want to restrict it for several reasons, including the fact that if you
> don't, you get hideous error messages when you use a type with a template that
> it doesn't work with.

That's not the reason I shouldn't be using it. The compiler should give precise error messages.

For instance, first I wrote this code:

void foo(T)(T param) {
  return param * 2;
}

I got these messages back:

main.d(4): Error: * has no effect in expression (param * 2)
main.d(20): Error: template instance main.foo!(int) error instantiating
main.d(20): Error: variable main.main.x voids have no value
main.d(20): Error: expression foo(3) is void and has no value

Umm... can't it just tell me "foo's return type is void and you are trying to return an int"?
June 09, 2011
On 2011-06-08 19:27, Ary Manzana wrote:
> On 6/8/11 11:56 PM, Jonathan M Davis wrote:
> > On 2011-06-08 02:20, Ary Manzana wrote:
> >> On 6/7/11 9:50 PM, Andrei Alexandrescu wrote:
> >>> On 6/7/11 5:35 AM, Ary Manzana wrote:
> >>>> Well, in Ruby every parameter type is auto. And it works pretty well.
> >>>> 
> >>>> :-)
> >>>> 
> >>>> When you invoke a method and something goes wrong, like the auto parameter doesn't have a method, it gives a sensible error message.
> >>> 
> >>> It does, just at run time. The way dynamic languages and static languages set up computation is very different and as such difficult to compare something as core as function call resolution as equals for equals.
> >>> 
> >>>> So:
> >>>> 
> >>>> void foo(auto parameter) {
> >>>> return parameter * 2;
> >>>> }
> >>>> 
> >>>> could just be the same as
> >>>> 
> >>>> void foo(T)(T parameter) {
> >>>> return parameter * 2;
> >>>> }
> >>>> 
> >>>> just with a nicer syntax (but I understand it doesn't add much).
> >>> 
> >>> The counterpoint is that you seldom want to write foo() as written above. You want to clarify what family of types it is intended for.
> >> 
> >> Why? Note that in Ruby you *NEVER* say the family of types, and that is a huge bennefit. Good tests and good documentation are better than restricting the time. Look at this:
> >> 
> >> ====================================================== import std.stdio;
> >> 
> >> // This is Ruby :-)
> >> auto foo(T)(T param) {
> >> return param * 2;
> >> }
> >> 
> >> class MyClass {
> >> int value;
> >> 
> >> this(int value) {
> >> this.value = value;
> >> }
> >> 
> >> int opBinary(string op)(int other) if (op == "*") {
> >> return value * other;
> >> }
> >> }
> >> 
> >> int main() {
> >> auto x = foo(3);
> >> auto y = foo(6L);
> >> auto z = foo(new MyClass(10));
> >> 
> >> writefln("%s", x);
> >> writefln("%s", y);
> >> writefln("%s", z);
> >> return 0;
> >> }
> >> ======================================================
> >> 
> >> This function:
> >> 
> >> auto foo(T)(T param) {
> >> return param * 2;
> >> }
> >> 
> >> Could be just as well:
> >> 
> >> auto foo(auto param) {
> >> return param * 2;
> >> }
> >> 
> >> Yes, in Ruby it is checked on runtime, on D it is checked on compile-time.
> >> 
> >> What's cool about "auto param"? I have written a function once and it will work for everything that can be multiplied and it will be compiled efficiently for every type that I pass that complies to the function. Why you find it useless? Why do I have to restrict the type? I can restrict the type just for the operations being performed on it and then my function is open to any param that accepts those operations.
> >> 
> >> I know, I know. This is the same as just a template, but with a different syntax. I just don't understand why you think you will almost always would like to restrict the type you pass to the function. If you do, you limit your code. If you don't, you open your code and you have to write less code.
> > 
> > You want to restrict it for several reasons, including the fact that if you don't, you get hideous error messages when you use a type with a template that it doesn't work with.
> 
> That's not the reason I shouldn't be using it. The compiler should give precise error messages.
> 
> For instance, first I wrote this code:
> 
> void foo(T)(T param) {
>    return param * 2;
> }
> 
> I got these messages back:
> 
> main.d(4): Error: * has no effect in expression (param * 2)
> main.d(20): Error: template instance main.foo!(int) error instantiating
> main.d(20): Error: variable main.main.x voids have no value
> main.d(20): Error: expression foo(3) is void and has no value
> 
> Umm... can't it just tell me "foo's return type is void and you are trying to return an int"?

There are definitely things that can be done to improve error messages, but providing good error messages for templates is notoriously hard. C++ has been at it for years, and its error messages are still pretty awful regardless of the compiler you're using. Remember that when you're dealing with templates, you're compiling generated code, which complicates things a fair bit. If it gives you error messages like it does if you had written the code by hand (which is what it frequently ends up doing, if not always), then the messages tend to be incredibly obtuse, because you may not have written the code which is failing, and there's a good chance that error message means nothing to you. And if it doesn't treat the templates as normal code when dealing with errors, then that complicates template compilation a fair bit, because you have to duplicate all kinds of error checking and error reporting stuff. And how does the compiler know whether it's an error in the template itself or if it's an error simply because you gave it a template argument which just doesn't work with that template?

I don't think that there's any question that template error messages could be improved, and they likely will be, but by their very nature, reporting template-related errors seems to be a difficult task to adequately solve. It's one of the things that many people complain about with C++. Template constraints improve the situation considerably.

- Jonathan M Davis
June 09, 2011
On 6/9/11 9:56 AM, Jonathan M Davis wrote:

> I don't think that there's any question that template error messages could be
> improved, and they likely will be, but by their very nature, reporting
> template-related errors seems to be a difficult task to adequately solve. It's
> one of the things that many people complain about with C++. Template
> constraints improve the situation considerably.

I don't think the problem is with templates. If I try to compile this:

===
void foo(int param) {
  return param * 2;
}

void main() {
  foo(3);
}
===

it says:

main.d(2): Error: * has no effect in expression (param * 2)

WTF? Don't make me think. I just really want to know that I'm returning from a void function and I can't do that.

Let's assign foo(3) to something:

===
void foo(int param) {
  return param * 2;
}

void main() {
  auto x = foo(3);
}
===

It says:

main.d(2): Error: * has no effect in expression (param * 2)
main.d(6): Error: variable main.main.x voids have no value
main.d(6): Error: expression foo(3) is void and has no value

Really? Voids have no value? That's good to know, I didn't know that voids have no value.

Mmm... those are messages to be read by the compiler, not by the user. The compiler is expecting something that's not void and it is getting a void thing. The message it generates is "Voids has no value".

The problem are not templates or templates messages. The problem is that the very basic compiler error messages are targeted at the compiler, not the developer.

And then this:

===
void main() {
  const x = 1;
  x = 3;
}
===

main.d(3): Error: variable main.main.x cannot modify const

What do you mean when you say that the variable x cannot modify const? Wait, I desguise myself as Tarzan and now I understand. It should be "Error: trying to modify const variable main.main.x".

There is an excellent book called "Don't make me think". When I see "variable main.main.x cannot modify const" I have to think a little to understand that that means "variable main.main.x is const and cannot be modified". Don't make me think. :-)
June 09, 2011
On 2011-06-09 00:19, Ary Manzana wrote:
> On 6/9/11 9:56 AM, Jonathan M Davis wrote:
> > I don't think that there's any question that template error messages could be improved, and they likely will be, but by their very nature, reporting template-related errors seems to be a difficult task to adequately solve. It's one of the things that many people complain about with C++. Template constraints improve the situation considerably.
> 
> I don't think the problem is with templates. If I try to compile this:
> 
> ===
> void foo(int param) {
>    return param * 2;
> }
> 
> void main() {
>    foo(3);
> }
> ===
> 
> it says:
> 
> main.d(2): Error: * has no effect in expression (param * 2)
> 
> WTF? Don't make me think. I just really want to know that I'm returning from a void function and I can't do that.
> 
> Let's assign foo(3) to something:
> 
> ===
> void foo(int param) {
>    return param * 2;
> }
> 
> void main() {
>    auto x = foo(3);
> }
> ===
> 
> It says:
> 
> main.d(2): Error: * has no effect in expression (param * 2)
> main.d(6): Error: variable main.main.x voids have no value
> main.d(6): Error: expression foo(3) is void and has no value
> 
> Really? Voids have no value? That's good to know, I didn't know that voids have no value.
> 
> Mmm... those are messages to be read by the compiler, not by the user. The compiler is expecting something that's not void and it is getting a void thing. The message it generates is "Voids has no value".
> 
> The problem are not templates or templates messages. The problem is that the very basic compiler error messages are targeted at the compiler, not the developer.
> 
> And then this:
> 
> ===
> void main() {
>    const x = 1;
>    x = 3;
> }
> ===
> 
> main.d(3): Error: variable main.main.x cannot modify const
> 
> What do you mean when you say that the variable x cannot modify const? Wait, I desguise myself as Tarzan and now I understand. It should be "Error: trying to modify const variable main.main.x".
> 
> There is an excellent book called "Don't make me think". When I see "variable main.main.x cannot modify const" I have to think a little to understand that that means "variable main.main.x is const and cannot be modified". Don't make me think. :-)

Template error messages _do_ tend to be hideous. But that doesn't mean that normal errors are the best either. And as I said, templates often end up reporting exactly the same errors that you'd get if you wrote the code by hand, it's just that the errors are then in the template and generally harder to understand, because there's a good chance that it's not the code that you write.

But regardless, none of these error messages are intended to be "read by the compiler." They're all intended to be read by humans. It's just that you don't find the choice of wording to be particularly understandable. However, everything that it's saying is correct. If you find particular error messages to be bad - particularly if you can think of better alternatives - then create enhancement requests for them in bugzilla. Then perhaps we'll get better error messages. But remember that the compiler doesn't know what you're trying to do. It only knows what you told it to do, so its messages tend to be targetted at telling you that what you're doing isn't working rather than telling you what you should be doing instead. So, error messages are often not that great simply because the compiler is not a mind-reader. Still, if you have suggestions for improvements to error messages, please submit them to bugzilla so that the error messages can be improved.

- Jonathan M Davis