November 13, 2018
On Monday, 12 November 2018 at 21:38:27 UTC, Walter Bright wrote:
> On 11/12/2018 8:28 AM, 12345swordy wrote:
>> The issue that I see is unintended implicit conversation when passing values to functions that have both int and bool overloads.
>
> The exact same thing happens when there are both int and short overloads.
>
> The underlying issue is is bool a one bit integer type, or something special? D defines it as a one bit integer type, fitting it into the other integer types using exactly the same rules.
>
> If it is to be a special type with special rules, what about the other integer types? D has a lot of basic types :-)

Ok, you don't want to introduce special rules for integers, and that understandable.
However there needs be a tool for the programmer to prevent unwanted implicit conversation when it comes to other users passing values to their public overload functions.(Unless there is already a zero cost abstraction that we are not aware of).

-Alex
November 12, 2018
On Tue, Nov 13, 2018 at 02:12:30AM +0000, 12345swordy via Digitalmars-d-announce wrote:
> On Monday, 12 November 2018 at 21:38:27 UTC, Walter Bright wrote:
[...]
> > The underlying issue is is bool a one bit integer type, or something special? D defines it as a one bit integer type, fitting it into the other integer types using exactly the same rules.
> > 
> > If it is to be a special type with special rules, what about the other integer types? D has a lot of basic types :-)
> 
> Ok, you don't want to introduce special rules for integers, and that understandable.
>
> However there needs be a tool for the programmer to prevent unwanted implicit conversation when it comes to other users passing values to their public overload functions.(Unless there is already a zero cost abstraction that we are not aware of).
[...]

This discussion makes me want to create a custom bool type that does not allow implicit conversion. Something like:

	struct Boolean {
		private bool impl;
		static Boolean True = Boolean(1);
		static Boolean False = Boolean(0);

		// For if(Boolean b)
		opCast(T : bool)() { return impl; }

		...
	}

Unfortunately, it wouldn't quite work because there's no way for built-in comparisons to convert to Boolean instead of bool. So you'd have to manually surround everything with Boolean(...), which is a severe usability handicap.


T

-- 
People tell me that I'm skeptical, but I don't believe them.
November 13, 2018
On Monday, 12 November 2018 at 21:38:27 UTC, Walter Bright wrote:
> On 11/12/2018 8:28 AM, 12345swordy wrote:
>> The issue that I see is unintended implicit conversation when passing values to functions that have both int and bool overloads.
>
> The exact same thing happens when there are both int and short overloads.
>
> The underlying issue is is bool a one bit integer type, or something special? D defines it as a one bit integer type, fitting it into the other integer types using exactly the same rules.
>
> If it is to be a special type with special rules, what about the other integer types? D has a lot of basic types :-)

You nailed it on the head.

The only sensible course of action, therefore, is to give programmers the option to disable implicit conversions, completely (or if doable, more precisely).

And while you're thinking about how to do that, can you also please think about how to give the programmer the option to enforce privacy on variable/method within a module.

Programmers just want to be able to write code that is more likely to be correct, than not, and have the compiler catch it when it's not.

You want to attract such programmers, or not?

November 13, 2018
On Monday, 12 November 2018 at 22:07:39 UTC, Walter Bright wrote:
> On 11/12/2018 12:34 PM, Neia Neutuladh wrote:
>> Tell me more about this "consistency".
>
> int f(short s) { return 1; }
> int f(int i) { return 2; }
>
> enum : int { a = 0 }
> enum A : int { a = 0 }
>
> pragma (msg, f(a));   // calls f(int)
> pragma (msg, f(A.a)); // calls f(short)
>
> I.e. it's consistent.
>
> Here's how it works:
>
> f(a): `a` is a manifest constant of type `int`, and `int` is an exact match for f(int), and f(short) requires an implicit conversion. The exact match of f(int) is better.
>
> f(A.a): `a` is an enum of type `A`. `A` gets implicitly converted to `int`. The `int` then gets exact match to f(int), and an implicit match to f(short). The sequence of conversions is folded into one according to:
>
>     <implicit conversion> <exact>               => <implicit conversion>
>     <implicit conversion> <implicit conversion> => <implicit conversion>

Doesn't the above miss a step, and wouldn't it be:

1) A.a => <implicit-convert-to-int><exact-match-on-f(int)>
2) A.a => <implicit-convert-to-int><implicit-convert-to-short><exact-match-on-f(short)>

So basically for the f(short) path you have 3 steps instead of 2 for the f(int) path.

So does it matter how many implicit conversions need to happen before D stops trying? Or is it basically convert as long as you can? Does D actually do a "find the shortest path via implicit conversions to an overload" algorithm?


>
> One could have <implicit conversion><exact> be treated as "better than" <implicit conversion><implicit conversion>, and it sounds like a good idea, but even C++, not known for simplicity, tried that and had to abandon it as nobody could figure it out once the code examples got beyond trivial examples.

Interesting. This seems simpler intuitively (shorter path, pick it), so I'm wondering if there're any links you can point to that describe what these problems were?

Cheers,
- Ali


November 13, 2018
On Tuesday, 13 November 2018 at 07:13:01 UTC, NoMoreBugs wrote:
>
> You nailed it on the head.
>
> The only sensible course of action, therefore, is to give programmers the option to disable implicit conversions, completely (or if doable, more precisely).
>
> And while you're thinking about how to do that, can you also please think about how to give the programmer the option to enforce privacy on variable/method within a module.
>
> Programmers just want to be able to write code that is more likely to be correct, than not, and have the compiler catch it when it's not.
>
> You want to attract such programmers, or not?

ok...not such a great idea (the compiler switch), after I thought more about that idea.

but some sort of annotation, and the programmers intent could become *much* clearer (allowing the programmer - and those reading it - to better reason about the correctness of the code):

bool(this) b; // compiler not allowed to do implicit conversions when assigning on b
double(this) d; // compiler not allowed to do implicit conversions when assigning to d

class C  //(or struct)
{
 private(this) password; // compiler will not allow other code outside of this type,
                        //  (but in the same module) to directly access password.
}


i.e (this) is just an annotation, for saying:

"I own this type, and the type needs to stay the way it's defined - no exceptions".

Too much? The language could not handle it? The programmer would never want it?


November 13, 2018
On 11/13/2018 12:23 AM, aliak wrote:
> Doesn't the above miss a step, and wouldn't it be:
> 
> 1) A.a => <implicit-convert-to-int><exact-match-on-f(int)>
> 2) A.a => <implicit-convert-to-int><implicit-convert-to-short><exact-match-on-f(short)>
> 
> So basically for the f(short) path you have 3 steps instead of 2 for the f(int) path.
> 
> So does it matter how many implicit conversions need to happen before D stops trying? Or is it basically convert as long as you can? Does D actually do a "find the shortest path via implicit conversions to an overload" algorithm?

It is not a shortest path algorithm. It's simply the enum is converted to the base type and the base type is matched against the parameter type.


>> One could have <implicit conversion><exact> be treated as "better than" <implicit conversion><implicit conversion>, and it sounds like a good idea, but even C++, not known for simplicity, tried that and had to abandon it as nobody could figure it out once the code examples got beyond trivial examples.
> 
> Interesting. This seems simpler intuitively (shorter path, pick it), so I'm wondering if there're any links you can point to that describe what these problems were?

No, I simply remember the discussions about it in the early 90's. Yes, it seems to intuitively make sense, but if you look at real C++ code and try to figure it out, it's a nightmare. There can also be multiple paths of conversions, and loops in those paths. There's a quadratic problem when there are multiple parameters.

November 13, 2018
On 11/12/18 4:38 PM, Walter Bright wrote:
> On 11/12/2018 8:28 AM, 12345swordy wrote:
>> The issue that I see is unintended implicit conversation when passing values to functions that have both int and bool overloads.
> 
> The exact same thing happens when there are both int and short overloads.
> 
> The underlying issue is is bool a one bit integer type, or something special? D defines it as a one bit integer type, fitting it into the other integer types using exactly the same rules.

D's definition is wanting. Most integer types act differently than bool:

1. Integer types can be incremented, bool cannot
2. Integer types truncate by removing the extraneous bits, bool truncates to `true` for all values except 0.
3. Integer types have signed and unsigned variants, bool does not.
4. Integer types allow negation, bool does not.
5. Integer types can be used in a foreach(x; v1 .. v2), bool cannot.

It is true that bools act similarly to a 1-bit integer type in many cases, but only via promotion. That is, they *convert* to 1-bit integers, but don't behave like integers in their own type.

Regarding enums with base types, I admit I would totally expect an enum based on int to match an int overload over a short overload.

You don't think this is confusing to an average developer?

import std.stdio;

void foo(int x)
{
    writeln("integer");
}

void foo(short x)
{
    writeln("short");
}

enum A : int
{
    a = 1,
    b = 2,
    c = 3
}

void main()
{
    auto a = A.a;
    foo(A.a); // case 1
    foo(a);   // case 2
}

case 1 prints short, but case 2 prints integer. Both are passed the same value. This comes into play when using compile-time generation -- you are expecting the same behavior when using the same values. This is super-confusing.

But on the other hand, an A can ONLY be 3 or less, so why doesn't case 2 print short? If VRP is used here, it seems lacking.

Maybe the biggest gripe here is that enums don't prefer their base types over what their base types convert to. In the developer's mind, the conversion is:

A => int => (via VRP) short

which seems more complex than just

A => int

> If it is to be a special type with special rules, what about the other integer types? D has a lot of basic types :-)

The added value of having bool implicitly cast to integer types is great. I wouldn't want to eliminate that. The other way around seems of almost no value, except maybe to avoid extra code in the compiler.

So, I would be fine to have bool be a non-integer type that implicitly casts to integer for use in math or other reasons. But having 1 or 0 implicitly cast to true or false has little value, and especially using it as "just another 1-bit integer", which it really isn't, has almost no usage.

-Steve
November 13, 2018
On Monday, 12 November 2018 at 09:45:14 UTC, Mike Parker wrote:
> DIP 1015, "Deprecation and removal of implicit conversion from integer and character literals to bool, has been rejected, primarily on the grounds that it is factually incorrect in treating bool as a type distinct from other integral types.
>
> The TL;DR is that the DIP is trying to change behavior that is working as intended.
>
> From Example A in the DIP:
>
>     bool b = 1;
>
> This works because bool is a "small integral" with a range of 0..1. The current behavior is consistent with all other integrals.
>
> From Example B in the DIP:
>
> ```
> int f(bool b) { return 1; }
> int f(int i) { return 2; }
>
> enum E : int
> {
>     a = 0,
>     b = 1,
>     c = 2,
> }
> ```
>
> Here, f(a) and f(b) call the bool overload, while f(c) calls the int version. This works because D selects the overload with the tightest conversion. This behavior is consistent across all integral types. Replace bool with ubyte and f(a), f(b) would both call the ubyte version. The same holds for the DIP's Example C.
>
> Walter and Andrei left the door open to change the overload behavior for *all* integral types, with the caveat that it's a huge hurdle for such a DIP to be accepted. It would need a compelling argument.
>
> You can read a few more details in the summary I appended to the DIP:
>
> https://github.com/dlang/DIPs/blob/master/DIPs/rejected/DIP1015.md#formal-assessment
>
> Thanks to Mike Franklin for sticking with the process to the end.

I was going to write something up about how you can't do arithmetic on bool types therefore they aren't integral, but I tested and realized D allows this (i.e. bool + bool, bool * bool). Still that seems nonsensical, so I guess my question what is the definition of an integral type and how does bool fit?
November 13, 2018
On Tuesday, 13 November 2018 at 16:26:55 UTC, Chris M. wrote:
> On Monday, 12 November 2018 at 09:45:14 UTC, Mike Parker wrote:
>> [...]
>
> I was going to write something up about how you can't do arithmetic on bool types therefore they aren't integral, but I tested and realized D allows this (i.e. bool + bool, bool * bool). Still that seems nonsensical, so I guess my question what is the definition of an integral type and how does bool fit?

Addendum: definition of an integral type in Walter/Andrei's mind
November 13, 2018
On 11/13/18 11:26 AM, Chris M. wrote:
> On Monday, 12 November 2018 at 09:45:14 UTC, Mike Parker wrote:
>> DIP 1015, "Deprecation and removal of implicit conversion from integer and character literals to bool, has been rejected, primarily on the grounds that it is factually incorrect in treating bool as a type distinct from other integral types.
>>
>> The TL;DR is that the DIP is trying to change behavior that is working as intended.
>>
>> From Example A in the DIP:
>>
>>     bool b = 1;
>>
>> This works because bool is a "small integral" with a range of 0..1. The current behavior is consistent with all other integrals.
>>
>> From Example B in the DIP:
>>
>> ```
>> int f(bool b) { return 1; }
>> int f(int i) { return 2; }
>>
>> enum E : int
>> {
>>     a = 0,
>>     b = 1,
>>     c = 2,
>> }
>> ```
>>
>> Here, f(a) and f(b) call the bool overload, while f(c) calls the int version. This works because D selects the overload with the tightest conversion. This behavior is consistent across all integral types. Replace bool with ubyte and f(a), f(b) would both call the ubyte version. The same holds for the DIP's Example C.
>>
>> Walter and Andrei left the door open to change the overload behavior for *all* integral types, with the caveat that it's a huge hurdle for such a DIP to be accepted. It would need a compelling argument.
>>
>> You can read a few more details in the summary I appended to the DIP:
>>
>> https://github.com/dlang/DIPs/blob/master/DIPs/rejected/DIP1015.md#formal-assessment 
>>
>>
>> Thanks to Mike Franklin for sticking with the process to the end.
> 
> I was going to write something up about how you can't do arithmetic on bool types therefore they aren't integral, but I tested and realized D allows this (i.e. bool + bool, bool * bool). Still that seems nonsensical, so I guess my question what is the definition of an integral type and how does bool fit?

What it's doing is promoting false to an integer 0 and true to an integer 1. These work just like all the other integer promotion rules.

Interestingly enough, since bool can only be promoted to 0 or 1, bool * bool can be assigned back to a bool, but bool + bool can't be assigned back to a bool unless you cast. But don't expect the addition to behave as other integers do, as true + true == 2, which then casts to true.

-Steve