www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - I have a feature request: "Named enum scope inference"

reply "Tommi" <tommitissari hotmail.com> writes:
I have a feature request: "Named enum scope inference"

The idea is, that whenever a named enum value is expected, you 
don't need to explicitly specify the scope of the enum value. 
This would reduce redundancy in typing, just like automatic type 
inference does.

Examples:
---------

enum MyDirection { forward, reverse }

struct MyIterator(MyDirection dir)
{
     ...
}

int forward = 42; // Doesn't interfere with the next line...
auto itr = MyIterator!forward(); // Infers MyDirection.forward

----------------------------------------------------------------

enum MyLockType { read, read_write }

struct MyScopedLock
{
     this(MyMutex mutex, MyLockType lockType)
     {
         ...
     }
}

shared MyMutex g_mutex;
...
auto scopedLock = MyScopedLock(g_mutex, read_write);
// Infered MyLockType.read_write

// Side note: Compare the above to having a boolean flag...
auto scopedLock = MyInferiorScopedLock(g_mutex, true);
// ... and you have to read the docs to know what 'true' means

----------------------------------------------------------------

enum MyFruit { apple, orange, banana }

MyFruit fruit;

switch (fruit)
{
     case apple:  break; // Case expressions know what type to
     case orange: break; // expect based on the switch expression
     case banana: break;
}
Sep 28 2012
next sibling parent reply =?UTF-8?B?QWxleCBSw7hubmUgUGV0ZXJzZW4=?= <alex lycus.org> writes:
On 29-09-2012 03:55, Tommi wrote:
 I have a feature request: "Named enum scope inference"

 The idea is, that whenever a named enum value is expected, you don't
 need to explicitly specify the scope of the enum value. This would
 reduce redundancy in typing, just like automatic type inference does.

 Examples:
 ---------

 enum MyDirection { forward, reverse }

 struct MyIterator(MyDirection dir)
 {
      ...
 }

 int forward = 42; // Doesn't interfere with the next line...
 auto itr = MyIterator!forward(); // Infers MyDirection.forward

 ----------------------------------------------------------------

 enum MyLockType { read, read_write }

 struct MyScopedLock
 {
      this(MyMutex mutex, MyLockType lockType)
      {
          ...
      }
 }

 shared MyMutex g_mutex;
 ...
 auto scopedLock = MyScopedLock(g_mutex, read_write);
 // Infered MyLockType.read_write

 // Side note: Compare the above to having a boolean flag...
 auto scopedLock = MyInferiorScopedLock(g_mutex, true);
 // ... and you have to read the docs to know what 'true' means

 ----------------------------------------------------------------

 enum MyFruit { apple, orange, banana }

 MyFruit fruit;

 switch (fruit)
 {
      case apple:  break; // Case expressions know what type to
      case orange: break; // expect based on the switch expression
      case banana: break;
 }

The first issue with this proposal that comes to mind is this: enum Foo { bar } void func(Foo f) { // ... } // ... Foo bar = Foo.bar; func(bar); // ? -- Alex Rønne Petersen alex lycus.org http://lycus.org
Sep 28 2012
parent =?UTF-8?B?QWxleCBSw7hubmUgUGV0ZXJzZW4=?= <alex lycus.org> writes:
On 29-09-2012 04:31, Tommi wrote:
 On Saturday, 29 September 2012 at 02:01:15 UTC, Alex Rønne Petersen wrote:
 The first issue with this proposal that comes to mind is this:

 enum Foo { bar }

 void func(Foo f)
 {
     // ...
 }

 // ...

 Foo bar = Foo.bar;
 func(bar); // ?

Maybe it should simply throw a compile error about the ambiguity of 'bar' in 'func(bar)'. But if the local variable 'bar' if not of Foo type, then there's no ambiguity. Or, if the local variable 'bar' is a compile time constant that evaluates to Foo.bar, then there's no ambiguity either.

Regardless of the conditions under which to throw an error, it would be a breaking change. -- Alex Rønne Petersen alex lycus.org http://lycus.org
Sep 28 2012
prev sibling next sibling parent "Tommi" <tommitissari hotmail.com> writes:
On Saturday, 29 September 2012 at 02:01:15 UTC, Alex Rønne 
Petersen wrote:
 The first issue with this proposal that comes to mind is this:

 enum Foo { bar }

 void func(Foo f)
 {
     // ...
 }

 // ...

 Foo bar = Foo.bar;
 func(bar); // ?

Maybe it should simply throw a compile error about the ambiguity of 'bar' in 'func(bar)'. But if the local variable 'bar' if not of Foo type, then there's no ambiguity. Or, if the local variable 'bar' is a compile time constant that evaluates to Foo.bar, then there's no ambiguity either.
Sep 28 2012
prev sibling next sibling parent reply "David Piepgrass" <qwertie256 gmail.com> writes:
 I have a feature request: "Named enum scope inference"

 The idea is, that whenever a named enum value is expected, you 
 don't need to explicitly specify the scope of the enum value. 
 This would reduce redundancy in typing, just like automatic 
 type inference does.

 Examples:
 ---------

 enum MyDirection { forward, reverse }
 struct MyIterator(MyDirection dir)
 {
     ...
 }

 int forward = 42; // Doesn't interfere with the next line...
 auto itr = MyIterator!forward(); // Infers MyDirection.forward

I like the spirit of this feature, but as Alex pointed out, ambiguity is possible (which could theoretically cause errors in existing code) and while I'm not familiar with how the compiler is implemented, my spidey-sense thinks that what you're asking for could be tricky to implement (in a language that already has a very large amount of rules and features.) Plus, I don't like the fact that when you see something like "MyIterator!forward" by itself in code, there is no obvious clue that forward is an enum value and not a class name or a variable. So there is a sort of decrease in clarity of the entire language by increasing the total number of possible meanings that an identifier can have. So I think this feature would need a more clear syntax, something to indicate that the value is an enum value. I don't currently have a really good counterproposal though....
Sep 28 2012
next sibling parent reply =?UTF-8?B?QWxleCBSw7hubmUgUGV0ZXJzZW4=?= <alex lycus.org> writes:
On 29-09-2012 06:06, Tommi wrote:
 So, these would be the new rules we'd give to the compiler:

 1) See if you can compile the code exactly the way you've been doing it
 thus far. If it compiles, great, we're done.

 2) Else, if there are undefined identifiers that are passed to places
 where named enum variables are expected, try to see if prefixing those
 identifier names with the expected enum type name (plus a dot) would
 make the code compile.

 This wouldn't break any existing code, and to me the rule seems
 intuitive enough.

It's an awful lot of magic (it's not as easy in the implementation as it sounds like) for questionable gain when we have the with statement IMO. -- Alex Rønne Petersen alex lycus.org http://lycus.org
Sep 28 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 9/29/12 2:44 PM, Tommi wrote:
 But, if we were allowed to make a breaking change

I stopped reading here :o). Andrei
Sep 29 2012
prev sibling parent Ben Davis <entheh cantab.net> writes:
On 29/09/2012 04:11, Andrej Mitrovic wrote:
 On 9/29/12, David Piepgrass <qwertie256 gmail.com> wrote:
 I like the spirit of this feature, but as Alex pointed out,
 ambiguity is possible (which could theoretically cause errors in
 existing code)

It could also cause subtle problems because enum values are implicitly convertible to the enum's base type. Take this for example: void test(bool state) { } enum Foo { no, yes } class Class { enum Bar { yes, no } void test() { .test(no); } // pass Foo.no (true) or Bar.no (false) ? }

This isn't an issue with this proposal. There is no enum type available to use for inference, so 'no' simply doesn't resolve there. If you're ever working in Java using Eclipse, you'll notice that Eclipse has this very feature in its autocomplete engine. For example, if you write: if (myEnumValue == and then press Ctrl+Space, then it'll list the enum constants. When you select one, it'll generate the qualified name because it has to. The point is to use what we know about the type that is *likely* to occur here (by considering assignment LHS, comparison LHS, parameters to possibly matching methods, method return type in argument to 'return', etc.) to infer which enum type's members to consider (if any). Hope that clarifies :)
Sep 29 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, September 29, 2012 03:55:48 Tommi wrote:
 enum MyFruit { apple, orange, banana }
 
 MyFruit fruit;
 
 switch (fruit)
 {
      case apple:  break; // Case expressions know what type to
      case orange: break; // expect based on the switch expression
      case banana: break;
 }

This could be achieved by simply making it so that when a variable of an enum type is used in a switch statement, the cases permit you to omit the enum's type when referring to the enum values. None of the name inferrence stuff that you're suggesting would be required for that. Though at the moment, I believe that you could simply use with to solve the problem: with(MyFruit) { switch(fruit) { case apple: break; case orange: break; case banana: break; } } All that would be required to do it without the with would be to make it so that the compiler implicitly added the with when an enum type is used in the switch. - Jonathan M Davis
Sep 28 2012
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 9/29/12, David Piepgrass <qwertie256 gmail.com> wrote:
 I like the spirit of this feature, but as Alex pointed out,
 ambiguity is possible (which could theoretically cause errors in
 existing code)

It could also cause subtle problems because enum values are implicitly convertible to the enum's base type. Take this for example: void test(bool state) { } enum Foo { no, yes } class Class { enum Bar { yes, no } void test() { .test(no); } // pass Foo.no (true) or Bar.no (false) ? }
Sep 28 2012
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 9/29/12, Jonathan M Davis <jmdavisProg gmx.com> wrote:
 you could simply use with to solve the problem:

 with(MyFruit)
 {
     switch(fruit)
     {
         case apple: break;
         case orange: break;
         case banana: break;
     }
 }

It's even simpler: switch(fruit) with (MyFruit) { case apple: break; case orange: break; case banana: break; }
Sep 28 2012
prev sibling next sibling parent "Tommi" <tommitissari hotmail.com> writes:
On Saturday, 29 September 2012 at 02:37:40 UTC, Alex Rønne 
Petersen wrote:
 Regardless of the conditions under which to throw an error, it 
 would be a breaking change.

I guess that's a bad thing. Hmmm... too bad. Well... maybe we could make it so that variables of the requested enum type are looked up first, and if such is found (even with a same name as one of the enumerations), then that variable is used. Only if no variables of that name exist, then you see if the name correctly maps to a named enum literal, and if not then you see if you can map the name using the enum type name as its scope. I think that's quite a logical rule anyway, because if you've defined a variable of the correct enum type and pass it to a function, you quite likely meant to pass that variable to that function (instead of passing an enum literal).
Sep 28 2012
prev sibling next sibling parent "Tommi" <tommitissari hotmail.com> writes:
On Saturday, 29 September 2012 at 02:57:42 UTC, David Piepgrass 
wrote:
 Plus, I don't like the fact that when you see something like 
 "MyIterator!forward" by itself in code, there is no obvious 
 clue that forward is an enum value and not a class name or a 
 variable. So there is a sort of decrease in clarity of the 
 entire language by increasing the total number of possible 
 meanings that an identifier can have.

But, if you use the explicit "MyIterator!(MyDirection.forward)" in your code, I don't think that's any more clear about what "forward" actually is. It could be anything: struct MyDirection { struct forward {} // ... or: static property int forward() { return 42; } // ... or: static enum forward = 1.5; } Only extra clue that get with the the explicit form (EnumType.enumerationName) is the name of the enum type. If it makes the code clearer, then use explicit form. But more often than not, the combination of the enumeration name and the context where it is used makes the intention clear enough. On 9/29/12, David Piepgrass <qwertie256 gmail.com> wrote:
 
 It could also cause subtle problems because enum values are 
 implicitly
 convertible to the enum's base type. Take this for example:
 
 void test(bool state) { }
 enum Foo { no, yes }
 class Class
 {
    enum Bar { yes, no }
    void test() { .test(no); }  // pass Foo.no (true) or Bar.no 
 (false) ?
 }

But that's not what I'm suggesting. The feature suggested is: "Try to perform implicit scoping (as a last resort), if a named enum variable is expected". Your function "void test(bool state)" doesn't *expect* a named enum as an argument. If it did expect, say Foo, as an argument, then Foo.no would be passed.
Sep 28 2012
prev sibling next sibling parent "Tommi" <tommitissari hotmail.com> writes:
So, these would be the new rules we'd give to the compiler:

1) See if you can compile the code exactly the way you've been 
doing it thus far. If it compiles, great, we're done.

2) Else, if there are undefined identifiers that are passed to 
places where named enum variables are expected, try to see if 
prefixing those identifier names with the expected enum type name 
(plus a dot) would make the code compile.

This wouldn't break any existing code, and to me the rule seems 
intuitive enough.
Sep 28 2012
prev sibling next sibling parent "Tommi" <tommitissari hotmail.com> writes:
On Saturday, 29 September 2012 at 02:57:42 UTC, David Piepgrass 
wrote:
 Plus, I don't like the fact that when you see something like 
 "MyIterator!forward" by itself in code, there is no obvious 
 clue that forward is an enum value and not a class name or a 
 variable.

I might also add, that once we have an IDE that can do proper semantic analysis and colorization, you'll know it's an enum because it'll be colored like an enum.
Sep 28 2012
prev sibling next sibling parent "Tommi" <tommitissari hotmail.com> writes:
On Saturday, 29 September 2012 at 04:26:01 UTC, Alex Rønne 
Petersen wrote:
 It's an awful lot of magic (it's not as easy in the 
 implementation as it sounds like) for questionable gain when we 
 have the with statement IMO.

"it's not as easy in the implementation as it sounds like" ---------------------------------------------------------- If this argument means: "It's going to increase the compilation time too much", then it's obviously a good reason not to do it. But if it means: "It's a lot of work to modify the compiler source code", then that's completely an unacceptable argument. The question of how much work it is to implement, has nothing to do with whether it's a good feature to have or not. And that's what we're discussing. "questionable gain" ------------------- Well, if you never use enum flags to control the specifics of your types and functions, then the gain is zero. That is parallel to: if you never create any variables, then the gain of the keyword 'auto' is zero. If you do however do these things constantly, then the gain is: less typing and cleaner syntax. Comparison: enum Size { small, medium, big } enum Fitness { thin, strong, weak, fat } enum Goal { love, build, heal, kill } class Character(Size s, Fitness f, Motivation m) { ... } auto c1 = new Character!(Size.big, Fitness.fat, Goal.kill); auto c2 = new Character!(big, fat, kill);
Sep 28 2012
prev sibling next sibling parent "Bernard Helyer" <b.helyer gmail.com> writes:
On Saturday, 29 September 2012 at 05:08:12 UTC, Tommi wrote:
 then that's completely an unacceptable argument. The question 
 of how much work it is to implement, has nothing to do with 
 whether it's a good feature to have or not. And that's what 
 we're discussing.

Except a theoretical feature doesn't exist, so someone has to write the code. So no, it's not an 'unacceptable argument'.
Sep 28 2012
prev sibling next sibling parent kenji hara <k.hara.pg gmail.com> writes:
2012/9/29 Tommi <tommitissari hotmail.com>:
 On Saturday, 29 September 2012 at 04:26:01 UTC, Alex R=C3=B8nne Petersen =

 It's an awful lot of magic (it's not as easy in the implementation as it
 sounds like) for questionable gain when we have the with statement IMO.

"it's not as easy in the implementation as it sounds like" ---------------------------------------------------------- If this argument means: "It's going to increase the compilation time too much", then it's obviously a good reason not to do it. But if it means: "It's a lot of work to modify the compiler source code", then that's completely an unacceptable argument. The question of how much work it is =

 implement, has nothing to do with whether it's a good feature to have or
 not. And that's what we're discussing.


 "questionable gain"
 -------------------
 Well, if you never use enum flags to control the specifics of your types =

 functions, then the gain is zero. That is parallel to: if you never creat=

 any variables, then the gain of the keyword 'auto' is zero. If you do
 however do these things constantly, then the gain is: less typing and
 cleaner syntax. Comparison:

 enum Size    { small, medium, big }
 enum Fitness { thin, strong, weak, fat }
 enum Goal    { love, build, heal, kill }

 class Character(Size s, Fitness f, Motivation m)
 {
     ...
 }

 auto c1 =3D new Character!(Size.big, Fitness.fat, Goal.kill);
 auto c2 =3D new Character!(big, fat, kill);

No, compiler implementation is not a problem. In past, I've thought about such feature, but there is some difficulties to determine an obvious semantic. 1. If the enum member name is same as local variable. enum E { foo ,bar } void test(E e) {} void main() { auto bar =3D E.foo; test(bar); // 1a) test receives E.bar, or // 1b) E.foo from local variable 'var'? } If you select 1a, existing code will break. But, 1b will introduce more worse case. See next. 2. The semantics based on 'whether is undefined name or not' is fragile. First, you might write code such as the following. enum E { foo ,bar } void test(E e) {} void main() { test(foo); // in here, the name 'bar' is undefined, so test can receive E.foo } After a while, you may add a global variable in module scope. enum E { foo ,bar } int foo =3D 10; void test(E e) {} void main() { test(foo); // foo is defined, and look up module scope foo. // Then, now the code is broken implicitly! } This is a hijacking of local scope, and it is awful. ---- P.S. It seems to me the root problem is that using a raw-identifier for the start of the inference. If there is a symbol literal, the problem may be solved. test('bar); // bar is a symbol, so it does not refer any normal declarat= ions Kenji Hara
Sep 29 2012
prev sibling next sibling parent reply Iain Buclaw <ibuclaw ubuntu.com> writes:
On 29 September 2012 04:12, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:
 On 9/29/12, Jonathan M Davis <jmdavisProg gmx.com> wrote:
 you could simply use with to solve the problem:

 with(MyFruit)
 {
     switch(fruit)
     {
         case apple: break;
         case orange: break;
         case banana: break;
     }
 }

It's even simpler: switch(fruit) with (MyFruit) { case apple: break; case orange: break; case banana: break; }

This. :-) -- Iain Buclaw *(p < e ? p++ : p) = (c & 0x0f) + '0';
Sep 29 2012
parent deadalnix <deadalnix gmail.com> writes:
Le 29/09/2012 14:04, Bernard Helyer a écrit :
 Yeah, to respond to the larger topic, the with statement
 is more than enough here. I'm not convinced that complicating
 lookup rules further is worth it.

Well, they are not complicated, they are mostly undefined.
Oct 01 2012
prev sibling next sibling parent "Bernard Helyer" <b.helyer gmail.com> writes:
Yeah, to respond to the larger topic, the with statement
is more than enough here. I'm not convinced that complicating
lookup rules further is worth it.
Sep 29 2012
prev sibling next sibling parent "Tommi" <tommitissari hotmail.com> writes:
On Saturday, 29 September 2012 at 05:47:59 UTC, Bernard Helyer 
wrote:
 Except a theoretical feature doesn't exist, so someone has
 to write the code. So no, it's not an 'unacceptable
 argument'.

I'll explain my way of seeing this in the form we all understand: code. bool tryImplement(Feature x) { bool is_a_nice_feature_to_have = discussFeature(x); if (is_a_nice_feature_to_have) { bool do_we_implement_it = discussImplementation(x); if (do_we_implement_it) { implement(x); return true; } std.pause(10.years); bool does_someone_else_implement_it_in_2022 = discussImplementation(x); if (does_someone_else_implement_it_in_2022) { implement(x); return true; } std.pause(10.years); // ... and so on } return false; } And I think we're currently inside the function call 'discussFeature(x)'. There's no point in calling 'discussImplementation(x)' until we're in the scope of the if clause 'if (is_a_nice_feature_to_have)'.
Sep 29 2012
prev sibling next sibling parent "Tommi" <tommitissari hotmail.com> writes:
On Saturday, 29 September 2012 at 07:04:19 UTC, kenji hara wrote:
 After a while, you may add a global variable in module scope.

   enum E { foo ,bar }
   int foo = 10;
   void test(E e) {}
   void main() {
     test(foo);  // foo is defined, and look up module scope foo.
     // Then, now the code is broken implicitly!
   }

 This is a hijacking of local scope, and it is awful.

That code doesn't compile anyway, because the global foo is an int. But you're right, my suggestion doesn't work. Or, there's no way to implement it without breaking existing code. Here's the reason again for completeness sake: enum E { foo, bar }; void test(E e) {} int intValue = -1; void main() { int intValue = 42; // Hides the global intValue. E foo = E.bar; // This is fine. A local variable foo hides // E.foo from 'implicit global visibility'. // It's effectively same as hiding a global. test(foo); // Calls test(E.bar) } ----------------------------------------------------- enum E { foo, bar }; void test(E e) {} void main() { test(foo); // Calls test(E.bar) } E foo = E.bar; // This is very bad. A global variable foo hides // E.foo from being implicitly globally visible // (where applicaple) On Saturday, 29 September 2012 at 07:04:19 UTC, kenji hara wrote:
 It seems to me the root problem is that using a raw-identifier 
 for the start of the inference.
 If there is a symbol literal, the problem may be solved.

   test('bar);  // bar is a symbol, so it does not refer any 
 normal declarations

That would work. I'd be fine with this kind of wild-card enum literal.
Sep 29 2012
prev sibling next sibling parent "Tommi" <tommitissari hotmail.com> writes:
But, if we were allowed to make a breaking change, then this is 
how I think it should work:

// in a module scope...

enum E { foo, bar };

-------------------------------------------------------------

// These cause a compile error "foo is ambiguous":
1) E foo = E.bar;
2) E foo = E.foo;
3) enum foo = E.bar;
4) immutable foo = E.bar;
5) immutable foo = initFoo(); // if we can't CTFE initFoo()

// These are fine:
1) enum foo = E.foo;
2) immutable foo = E.foo;
3) int foo = 42; // int isn't implicitly convertible to E
4) E Foo = E.bar;

-------------------------------------------------------------

struct Convertible
{
     E _value;

     alias _value this;
}

Convertible foo; // Compile error: foo is ambiguous

-------------------------------------------------------------

struct MyStruct(T)
{
     T foo = 123; // Fine, hides E.foo inside MyStruct scope

     void fun()
     {
         T foo = 42 // Fine, hides both MyStruct.foo and
                    // E.foo inside this function scope
     }
}

Here's the logic of it:
-----------------------
An enumeration of type E should be visible (like it was a module 
scope variable) if, and only if, it occurs in one of these 
"hot-spots" where a value of type E is expected (e.g. it could be 
in a template parameter list, function argument list, or in a 
case expression of a switch).

Inside these "hot-spots" the enumeration fights over visibility 
against variables. It loses that fight against function local and 
class/struct local variables which have the same name (and 
whatever type). But it wins the battle over visibility against 
module scope variables which have the same name but are not 
implicitly convertible to type E. Against global variables which 
are of type E or are implicitly convertible to type E, the battle 
over visibility ends in a tie, and that's a compile-time 
ambiguity error.
Sep 29 2012
prev sibling next sibling parent "Tommi" <tommitissari hotmail.com> writes:
Scratch my previous post. It had a weird rule where the types of 
identifiers had a say in whether or not there's ambiguity in the 
name lookup. That's just silly. It should always be an ambiguity 
error if the names are identical.

This new rule is easier to conceptualize too. Basically you just 
think that there are these "hot-spots" (they're red, I think) in 
your code wherever named enum values are expected. Inside each 
"hot-spot", the name lookup is allowed to think that all the 
enumerations (the enumerated identifiers) of that particular 
named enum type are in module scope. And that's it.

So, again... if we were allowed to make a breaking change, this 
is how I think it should work:

// in a module scope...

enum Char { a, b, c, d, e }

Char a = Char.c; // OK
auto b = a;      // OK: .b == Char.c
Char c = Char.a; // OK: .c == Char.a

Char d = a; // ERROR: 'a' could be either 'Char.a' or '.a'
             // because now 'a' is in a "hot-spot", where a
             // value convertible to type Char is expected,
             // and thus all Char enumerations can be seen
             // as if they were in module scope

int e = 42; // OK

void test(Char ch) {}

struct MyStruct
{
     int a = 1; // OK: hides '.a'

     void fun()
     {
         Char a = Char.e; // OK: hides 'MyStruct.a' and '.a'

         test(a); // OK: calls 'test(Char.e)' although the test
                  // argument 'a' is now in a "hot-spot" where
                  // all the enumerations of Char (a, b, c, d, e)
                  // are considered to be in module scope. But
                  // the function local 'a' hides anything that
                  // might be in module scope, so the name lookup
                  // doesn't even bother looking at module scope.

         test(e); // ERROR: 'e' could be either 'Char.e' or '.e'
                  // because now the name lookup has to look at
                  // the module scope, where we have also Char.e
                  // visible due to the fact that argument 'e'
                  // is in a "hot-spot". It doesn't matter from
                  // our name-lookup's point of view that test
                  // is not callable with an int value.
     }
}
Sep 29 2012
prev sibling next sibling parent "Tommi" <tommitissari hotmail.com> writes:
Although it's not very obvious what is a "hot-spot" and what is 
not.

enum Char { a, b, c, d }

Char a = c; // OK: 'c' is in a "hot-spot"

Char b = c + 1; // ERROR: 'c' is undefined, because it's not in
                 // a "hot-spot" and therefore Char enumerations
                 // aren't visible. The expression c + 1 is
                 // expected to return Char but there's no reason
                 // to expect the arguments of that expression to
                 // be of type Char. (Another error would be that
                 // c + 1 doesn't even return Char, but we never
                 // get that far because the name lookup fails)

int c = 42;

void test(Char ch) {}
void test(int val) {}

void main()
{
     test(c); // OK: calls test(.c) because arg 'c' is not in a
              // "hot-spot". Function 'test' isn't expecting a
              // Char variable as an argument, it's expecting a
              // type chosen from set of types (among which Char
              // just so happens to be). But, if you remove the
              // test(int) specialization, this function call
              // fails, and the one on the next line succeeds.

     test(d); // ERROR: 'd' is undefined, because it's not in a
              // "hot-spot" for the reason specified above, and
              // therefore enumerations of Char are not brought
              // into the module scope.
}

It is quite a mess. I think I'm ready to admit that this is not a 
feature we'd like to have in this language (probably not in any 
language for that matter).
Sep 29 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Saturday, 29 September 2012 at 02:57:42 UTC, David Piepgrass 
wrote:
 I have a feature request: "Named enum scope inference"

 The idea is, that whenever a named enum value is expected, you 
 don't need to explicitly specify the scope of the enum value. 
 This would reduce redundancy in typing, just like automatic 
 type inference does.

 Examples:
 ---------

 enum MyDirection { forward, reverse }
 struct MyIterator(MyDirection dir)
 {
    ...
 }

 int forward = 42; // Doesn't interfere with the next line...
 auto itr = MyIterator!forward(); // Infers MyDirection.forward

I like the spirit of this feature, but as Alex pointed out, ambiguity is possible (which could theoretically cause errors in existing code) and while I'm not familiar with how the compiler is implemented, my spidey-sense thinks that what you're asking for could be tricky to implement (in a language that already has a very large amount of rules and features.) Plus, I don't like the fact that when you see something like "MyIterator!forward" by itself in code, there is no obvious clue that forward is an enum value and not a class name or a variable. So there is a sort of decrease in clarity of the entire language by increasing the total number of possible meanings that an identifier can have. So I think this feature would need a more clear syntax, something to indicate that the value is an enum value. I don't currently have a really good counterproposal though....

+1
Sep 29 2012
prev sibling parent "Rob T" <rob ucora.com> writes:
On Saturday, 29 September 2012 at 23:49:47 UTC, Tommi wrote:
 It is quite a mess. I think I'm ready to admit that this is not 
 a feature we'd like to have in this language (probably not in 
 any language for that matter).

Using "with" should do the trick just fine for the few situations where there's too much redundant typing. I think adding new features needs to take a distant back seat to the far FAR more important issues in need of repair. For example, I cannot run D reliably from C/C++, yet that is one of the advertised features that compelled me to seriously consider D as a viable alternative to C/C++. What about dynamic linking? Nope, not yet. This is basic stuff! Should anyone really care about reducing the amount of typing you have to do when they can barely even use the language as it currently stands? There are however seemingly trivial items that probably should be added to D, but not as a convenience, instead to make it truely practical in a production environment. Now having said the above, the last thing D should become is stagnant for sake of preserving backwards compatibility. D2 is not D1, and D3 should not have to be D2. Personally, I'm in favor of the idea of breaking existing code so that past mistakes can be repaired and significant improvements can be implemented, but that should mean moving on to the next major version. --rt
Sep 29 2012