View mode: basic / threaded / horizontal-split · Log in · Help
March 23, 2010
Implicit enum conversions are a stupid PITA
I'm bringing this over here from a couple separate threads over on "D.learn" 
(My "D1: Overloading across modules" and bearophile's "Enum equality test").

Background summary:

bearophile:
> I'm looking for D2 rough edges. I've found that this D2 code
> compiles and doesn't assert at runtime:
>
> enum Foo { V1 = 10 }
> void main() {
>  assert(Foo.V1 == 10);
> }
>
> But I think enums and integers are not the same type,
> and I don't want to see D code that hard-codes comparisons
> between enum instances and number literals, so I think an
> equal between an enum and an int has to require a cast:
>
> assert(cast(int)(Foo.V1) == 10); // OK

He goes on to mention C++0x's "enum class" that, smartly, gets rid of that 
implicit conversion nonsense.

To put it simply, I agree with this even on mere principle. I'm convinced 
that the current D behavior is a blatant violation of strong-typing and 
smacks way too much of C's so-called "type system".

But here's another reason to get rid it that I, quite coincidentally, 
stumbled upon right about the same time:

Me:
> In D1, is there any reason I should be getting an error on this?:
>
> // module A:
> enum FooA { fooA };
> void bar(FooA x) {}
>
> // module B:
> import A;
> enum FooB { fooB };
> void bar(FooB x) {}
>
> bar(FooB.fooB); // Error: A.bar conflicts with B.bar (WTF?)

In the resulting discussion (which included a really hackish workaround), it 
was said that this is because of a rule (that I assume exists in D2 as well) 
that basically goes "two functions from different modules are in conflict if 
they have the same name." I assume (and very much hope) that the rule also 
has a qualification "...but only if implicit conversion rules make it 
possible for one to hijack the other".

It was said that this is to prevent a function call from getting hijacked by 
merely importing a module (or making a change in an imported module). That I 
can completely agree with. But I couldn't understand why this would cause 
conflicts involving enums until I thought about implicit enum-to-base-type 
conversion and came up with this scenario:

// Module Foo:
enum Foo { foo }

// module A:
import Foo;
void bar(Foo x){}

// module B version 1:
import Foo; // Note: A is not imported yet
void bar(int x){}
bar(Foo.foo); // Stupid crap that should never be allowed in the first place

// module B version 2:
import Foo;
import A; // <- This line added
void bar(int x){}
bar(Foo.foo); // Now that conflict error *cough* "helps".

So thanks to the useless and dangerous ability to implicitly convert an enum 
to its base type, we can't have certain perfectly sensible cross-module 
overloads.

Although, frankly, I *still* don't see why "bar(SomeEnum)" and 
"bar(SomeOtherEnum)" should ever be in conflict (unless that's only D1, or 
if implicit base-type-to-enum conversions are allowed (which would make 
things even worse)).
March 23, 2010
Re: Implicit enum conversions are a stupid PITA
Nick Sabalausky Wrote:

> I'm bringing this over here from a couple separate threads over on "D.learn" 
> (My "D1: Overloading across modules" and bearophile's "Enum equality test").
> 
> Background summary:
> 
> bearophile:
> > I'm looking for D2 rough edges. I've found that this D2 code
> > compiles and doesn't assert at runtime:
> >
> > enum Foo { V1 = 10 }
> > void main() {
> >  assert(Foo.V1 == 10);
> > }
> >
> > But I think enums and integers are not the same type,
> > and I don't want to see D code that hard-codes comparisons
> > between enum instances and number literals, so I think an
> > equal between an enum and an int has to require a cast:
> >
> > assert(cast(int)(Foo.V1) == 10); // OK
> 
> He goes on to mention C++0x's "enum class" that, smartly, gets rid of that 
> implicit conversion nonsense.
> 
> To put it simply, I agree with this even on mere principle. I'm convinced 
> that the current D behavior is a blatant violation of strong-typing and 
> smacks way too much of C's so-called "type system".
> 
> But here's another reason to get rid it that I, quite coincidentally, 
> stumbled upon right about the same time:
> 
> Me:
> > In D1, is there any reason I should be getting an error on this?:
> >
> > // module A:
> > enum FooA { fooA };
> > void bar(FooA x) {}
> >
> > // module B:
> > import A;
> > enum FooB { fooB };
> > void bar(FooB x) {}
> >
> > bar(FooB.fooB); // Error: A.bar conflicts with B.bar (WTF?)
> 
> In the resulting discussion (which included a really hackish workaround), it 
> was said that this is because of a rule (that I assume exists in D2 as well) 
> that basically goes "two functions from different modules are in conflict if 
> they have the same name." I assume (and very much hope) that the rule also 
> has a qualification "...but only if implicit conversion rules make it 
> possible for one to hijack the other".
> 
> It was said that this is to prevent a function call from getting hijacked by 
> merely importing a module (or making a change in an imported module). That I 
> can completely agree with. But I couldn't understand why this would cause 
> conflicts involving enums until I thought about implicit enum-to-base-type 
> conversion and came up with this scenario:
> 
> // Module Foo:
> enum Foo { foo }
> 
> // module A:
> import Foo;
> void bar(Foo x){}
> 
> // module B version 1:
> import Foo; // Note: A is not imported yet
> void bar(int x){}
> bar(Foo.foo); // Stupid crap that should never be allowed in the first place
> 
> // module B version 2:
> import Foo;
> import A; // <- This line added
> void bar(int x){}
> bar(Foo.foo); // Now that conflict error *cough* "helps".
> 
> So thanks to the useless and dangerous ability to implicitly convert an enum 
> to its base type, we can't have certain perfectly sensible cross-module 
> overloads.
> 
> Although, frankly, I *still* don't see why "bar(SomeEnum)" and 
> "bar(SomeOtherEnum)" should ever be in conflict (unless that's only D1, or 
> if implicit base-type-to-enum conversions are allowed (which would make 
> things even worse)).
> 
> 

Hum...

+1 

What can I add, you said it all ;)
March 23, 2010
Re: Implicit enum conversions are a stupid PITA
Nick Sabalausky Wrote:

> I'm bringing this over here from a couple separate threads over on "D.learn" 
> (My "D1: Overloading across modules" and bearophile's "Enum equality test").
> 
> Background summary:
> 
> bearophile:
> > I'm looking for D2 rough edges. I've found that this D2 code
> > compiles and doesn't assert at runtime:
> >
> > enum Foo { V1 = 10 }
> > void main() {
> >  assert(Foo.V1 == 10);
> > }
> >
> > But I think enums and integers are not the same type,
> > and I don't want to see D code that hard-codes comparisons
> > between enum instances and number literals, so I think an
> > equal between an enum and an int has to require a cast:
> >
> > assert(cast(int)(Foo.V1) == 10); // OK
> 
> He goes on to mention C++0x's "enum class" that, smartly, gets rid of that 
> implicit conversion nonsense.
> 
> To put it simply, I agree with this even on mere principle. I'm convinced 
> that the current D behavior is a blatant violation of strong-typing and 
> smacks way too much of C's so-called "type system".
> 
> But here's another reason to get rid it that I, quite coincidentally, 
> stumbled upon right about the same time:
> 
> Me:
> > In D1, is there any reason I should be getting an error on this?:
> >
> > // module A:
> > enum FooA { fooA };
> > void bar(FooA x) {}
> >
> > // module B:
> > import A;
> > enum FooB { fooB };
> > void bar(FooB x) {}
> >
> > bar(FooB.fooB); // Error: A.bar conflicts with B.bar (WTF?)
> 
> In the resulting discussion (which included a really hackish workaround), it 
> was said that this is because of a rule (that I assume exists in D2 as well) 
> that basically goes "two functions from different modules are in conflict if 
> they have the same name." I assume (and very much hope) that the rule also 
> has a qualification "...but only if implicit conversion rules make it 
> possible for one to hijack the other".
> 
> It was said that this is to prevent a function call from getting hijacked by 
> merely importing a module (or making a change in an imported module). That I 
> can completely agree with. But I couldn't understand why this would cause 
> conflicts involving enums until I thought about implicit enum-to-base-type 
> conversion and came up with this scenario:
> 
> // Module Foo:
> enum Foo { foo }
> 
> // module A:
> import Foo;
> void bar(Foo x){}
> 
> // module B version 1:
> import Foo; // Note: A is not imported yet
> void bar(int x){}
> bar(Foo.foo); // Stupid crap that should never be allowed in the first place
> 
> // module B version 2:
> import Foo;
> import A; // <- This line added
> void bar(int x){}
> bar(Foo.foo); // Now that conflict error *cough* "helps".
> 
> So thanks to the useless and dangerous ability to implicitly convert an enum 
> to its base type, we can't have certain perfectly sensible cross-module 
> overloads.
> 
> Although, frankly, I *still* don't see why "bar(SomeEnum)" and 
> "bar(SomeOtherEnum)" should ever be in conflict (unless that's only D1, or 
> if implicit base-type-to-enum conversions are allowed (which would make 
> things even worse)).
> 
> 

This also interacts with the crude hack of "this enum is actually a constant". 
if you remove the implicit casts than how would you be able to do:
void foo(int p); 
enum { bar = 4 }; // don't remember the exact syntax here
foo(bar); // compile-error?!

I feel that enum needs to be re-designed. I think that C style "enums are numbers" are *bad*, *wrong* designs that expose internal implementation and the only valid design is that of Java 5.

e.g.
enum Color {blue, green}
Color c = Color.blue;
c++; // WTF?  should NOT compile

A C style enum with values assigned is *not* an enumeration but rather a set of meaningful integral values and should be represented as such.

This was brought up many many times in the NG before and based on past occurences will most likely never change.
March 23, 2010
Re: Implicit enum conversions are a stupid PITA
yigal chripun:
> This was brought up many many times in the NG before and based on past occurences will most likely never change.

If I see some semantic holes I'd like to see them filled/fixed, when possible. Keeping the muzzle doesn't improve the situation :-)

Bye,
bearophile
March 23, 2010
Re: Implicit enum conversions are a stupid PITA
yigal chripun Wrote:

> A C style enum with values assigned is *not* an enumeration but rather a set of meaningful integral values and should be represented as such.
> 

The above isn't accurate. I'll re-phrase:
The values assigned to the members of the enums are just properties of the members, they do not define their identity. 
void bar(int);
bar(Color.Red.rgb); // no-problem
bar(Color.Red); // compile-error
March 23, 2010
Re: Implicit enum conversions are a stupid PITA
bearophile Wrote:

> yigal chripun:
> > This was brought up many many times in the NG before and based on past occurences will most likely never change.
> 
> If I see some semantic holes I'd like to see them filled/fixed, when possible. Keeping the muzzle doesn't improve the situation :-)
> 
> Bye,
> bearophile

I agree with you about the gaping semantic hole. All I'm saying is that after bringing this so many times to discussion before I lost hope that this design choice will ever be re-considered.
March 23, 2010
Re: Implicit enum conversions are a stupid PITA
"yigal chripun" <yigal100@gmail.com> wrote in message 
news:hobg4b$12ej$1@digitalmars.com...
>
> This also interacts with the crude hack of "this enum is actually a 
> constant".
> if you remove the implicit casts than how would you be able to do:
> void foo(int p);
> enum { bar = 4 }; // don't remember the exact syntax here
> foo(bar); // compile-error?!
>

AIUI, That style enum is already considered different by the compiler 
anyway. Specifically, it's doesn't create any new type, whereas the other 
type of enum creates a new semi-weak type. I don't think it would be too big 
of a step to go one step further and change "this kind of enum creates a new 
semi-weak type" to "this kind of enum creates a new strong type". But yea, I 
absolutely agree that calling a manifest constant an "enum" is absurd. It 
still bugs the hell out of me even today, but I've largely shut up about it 
since Walter hasn't wanted to change it even though he seems to be the only 
one who doesn't feel it's a bad idea (and it's not like it causes practical 
problems when actually using the language...although I'm sure it must be a 
big WTF for new and prospective D users).


> I feel that enum needs to be re-designed. I think that C style "enums are 
> numbers" are *bad*, *wrong* designs that expose internal implementation 
> and the only valid design is that of Java 5.
>
> e.g.
> enum Color {blue, green}
> Color c = Color.blue;
> c++; // WTF?  should NOT compile
>
> A C style enum with values assigned is *not* an enumeration but rather a 
> set of meaningful integral values and should be represented as such.
>
> This was brought up many many times in the NG before and based on past 
> occurences will most likely never change.

I would hate to see enums lose the concept of *having* a base type and base 
values because I do find that to be extremely useful (Haxe's enums don't 
have a base type and, from direct experience with them, I've found that to 
be a PITA too). But I feel very strongly that conversions both to and from 
the base type need to be explicit. In fact, that was one of the things that 
was bugging me about C/C++ even before I came across D. D improves the 
situation of course, but it's still only half-way.
March 23, 2010
Re: Implicit enum conversions are a stupid PITA
Nick Sabalausky:
> It still bugs the hell out of me even today, but I've largely shut up about it 
> since Walter hasn't wanted to change it even though he seems to be the only 
> one who doesn't feel it's a bad idea (and it's not like it causes practical 
> problems when actually using the language...although I'm sure it must be a 
> big WTF for new and prospective D users).

Recently D2 has introduced the name "inout", that doesn't seem very linked to its semantic purpose. I think "auto_const",  "auto const" or "autoconst" are better.
The recently introduced "auto ref" is clear, but I think "auto_ref" or "autoref"are better still.

Bye,
bearophile
March 24, 2010
Re: Implicit enum conversions are a stupid PITA
Nick Sabalausky wrote:
> So thanks to the useless and dangerous ability to implicitly convert an enum 
> to its base type, we can't have certain perfectly sensible cross-module 
> overloads.

One thing being able to convert enum to it's base type does allow is this:

import std.stdio;

enum FLAG
{
  READ  = 0x1,
  WRITE = 0x2,
  OTHER = 0x4
}

void foo(FLAG flags)
{
  writeln("Flags = ", flags);
}

int main(string[] args)
{
  foo(FLAG.READ);
  foo(FLAG.READ|FLAG.WRITE);
  return 0;
}


I find being able to define bit flag values with an enum and combine 
them using | and |= or negate with &= ~flag etc very useful.

Languages without the implicit conversion typically give an error when 
using |, |= etc forcing you to cast, eg.
  foo((int)FLAG.READ|(int)FLAG.WRITE);
which, in addition, breaks type safety as you're casting to 'int'.

Alternately they require you to define |, |= etc for the 'strong' enum 
type which is a PITA, IMO.

Granted, that's probably the most 'correct' way for a strongly typed 
language to do things, but it just feels un-necessary for someone who is 
used to the convenience of the implicit conversion.


All that said, I also find method/function collision very annoying. 
True, it is easily solvable with alias, but that's always felt a bit 
hackish and messy to me.


So, imagining we have a strongly typed 'enum' with no implicit 
conversion.  Can we get back the convenience of being able to call 
numeric operators on them without casts or all the legwork involved?

Could the compiler not automatically generate them?  The downside is 
that this would result in multiple copies of what was essentially the 
same function for int and every enum type.

We could make the programmer ask for it with a special base type, eg.
  enum FLAG : numeric {}
but, ideally we want existing code to continue to work.

I wonder if the compiler could safely do the (cast) for us without 
loosing type safety?  i.e. case the enum to int, call the int operator, 
and cast the result back.

The result would be that this works:
  foo(FLAG.READ|FLAG.WRITE);

but this would error:
  foo(FLAG.READ|FLAG.WRITE|5);

and enum would appear to be a strong type, and would no longer collide 
on function resolution but we'd have the convenience of numeric 
operators - a common usage pattern for enums.  Are there other usage 
patterns this would break?

R
March 24, 2010
Re: Implicit enum conversions are a stupid PITA
Regan Heath:
> I find being able to define bit flag values with an enum and combine 
> them using | and |= or negate with &= ~flag etc very useful.

The cause of the problem here is that you are trying to use enums for a different purpose, as composable flags. In C# enums and flags are not the same thing, you can use the [Flags] attribute:
http://www.codeguru.com/vb/sample_chapter/article.php/c12963
The ridiculous thing of D development is that no one ever takes a look at C#, it often already contains a solution to problems we are just starting to find in D (or often that we just refuse to see in D). As they say: "Those who cannot learn from C# are doomed to re-invent it, often badly."

(In D you can solve this problem creating a flags struct, using a strategy similar to the one used by std.bitmanip.bitfields, but it feels hackish).

Bye,
bearophile
« First   ‹ Prev
1 2 3 4 5
Top | Discussion index | About this forum | D home