This project is read-only.

Local const type inference

Topics: C# Language Design
May 17, 2014 at 12:14 PM
Edited May 17, 2014 at 12:23 PM
C# allows type inference for local variables using the "var" keyword, but it does not allow it for local constants.

Granted, a syntax like "const var dimension = 3;" would be very weird, but what about
const dimension = 3;
On the downside, people might argue that there aren't that many types that support literals anyway, and most off them are primitive types that have short names already. The obvious exception to that being the "null" literal of course.

But I feel that this feature would bring symmetry to the language. Also, because the compiler has features like constant propagation, making const declarations easier is a Good Thing.
May 17, 2014 at 1:01 PM
Could also be expanded to include Optional Parameters
 mymethod ( x As Integer, Optional  IsCheck = False )
IsChecked is inferred to be an Boolean.
May 17, 2014 at 5:17 PM
Edited May 17, 2014 at 5:18 PM
I would think that type inference should be supported for fields which are either private, or whose initialization form is either a new expression, a typecast expression, or is a static factory method of the new type. I would not favor type inference for locals unless they were inferred to be some compiler-internal type which could accurately represent any numeric value, and could be implicitly convertible to the float or double (storing the best possible representation), or to any other numeric type which could accommodate its value, e.g.
const oneThird = 0.3333333333333333333333333333;
float a = oneThird ; // Assign 0.333333343f;
double b = oneThird ; // Assign 0.33333333333333331
Decimal c = oneThird ;  // Assign 0.3333333333333333333333333333m;
If one wishes to use a particular number as both a float and a double, and wants the best representation in each format, being able to specify it once would seem much cleaner than having to use separate representations and hope one doesn't accidentally do something like:
const oneThird = 0.333333333f;
double oneThirdAsDouble = = 0.33333333333333333;
float a = oneThird ; // Assign 0.333333343f;
double b = oneThird ; // Assign 0.3333333432674408
Note that the latter code would not produce any diagnostics, even though it's unlikely to yield the desired effect.
May 17, 2014 at 5:53 PM
Supercat,

You seem to ignore that local constants exist already, with precisely defined semantics. In my proposal, I simply suggest to allow type inference for them, in the exact same way that type inference works for local variables. Just syntactic sugar, no new semantics.

Your proposal is asking for a brand new "compiler-internal type which could accurately represent any numeric value". Ignoring the fact that it is simply impossible to express any numeric value, let alone represent it, that almost sounds like a C preprocessor definition, and it seems very unlikely to happen. Wanting this for fields as well sounds close to impossible, since it would require a runtime representation of this "any numeric value".

More importantly, such a new "any numeric value" type would certainly not be something I want. I understand the C# type system, and I don't want to learn a new one.

Last but not least, why would you not allow local const type inference for, say, a string?
const format = "R";

return "(" + x.ToString(format) + ", " + y.ToString(format) + ")";
My proposal is based on the assumption (which could be false), that this feature could be added easily, since both building blocks are in place already: local constants, and type inference as used for local variables. It requires no new mental model, very small impact on the syntax, no new scoping rules or anything like that. It would just allow writing some code more concisely, make it just a little bit easier to use local constants and as such promote their usage.
May 17, 2014 at 6:22 PM
Sorry for shoehorning on your idea; my concern about type inference with constants relates to the fact that there are places where the exact type of a constant is extremely important, and places where it is not. If one piece of code writes, e.g.
const bit30 = 0x0000000040000000;
const bit31 = 0x0000000080000000;
const bit32 = 0x0000000100000000;
That might appear reasonable, and for many purposes those definitions would be fine. If, however, someplace else in the code does
long1 &= ~bit30;
long2 &= ~bit31;
long3 &= ~bit32;
the effects would probably not be as intended. In order for latter code to work, bit31 must be of type Int64. The compiler's default type won't work (interestingly, the compiler-default type will work correctly with both bit30 and bit32.

If there were suffixes to expressly specify literals of types double, Int32, and UInt32 (with the semantics that if a value was not representable in the requested type the compiler would squawk rather than substituting a bigger one) , I would favor implicit typing of constants only with literals that are suffixed so as to unambiguously indicate the expected type [an alternative would be to allow implicit typing of unsuffixed literals of type double and Int32 only, but I think it's cleaner to allow the types of all literals to be specified].
May 17, 2014 at 8:17 PM
Edited May 17, 2014 at 8:19 PM
Supercat,

Like I said before, in my proposal the way the compile time type of an expression is determined is not altered from the current specification.

So, whatever the types of
const bit30 = 0x0000000040000000;
const bit31 = 0x0000000080000000;
const bit32 = 0x0000000100000000;
are, they would be the same as if you'd written:
var bit30 = 0x0000000040000000; // int
var bit31 = 0x0000000080000000; // uint
var bit32 = 0x0000000100000000; // long
So it looks like you are really criticizing an already existing feature, not my proposal.

That being said, for your example I would explicitly type them all to ulong, either using the ul suffix, either explicitly stating the type, either using a [Flags] enum with an underlying type of ulong. That does not imply that the proposed feature is useless though.

As a side note, there are suffixes for Double and UInt32 (d and u respectively). The d suffix is rarely used, as the type of 3.0 is double anyway; people rarely write 3d.
May 17, 2014 at 8:45 PM
From my trek through code shrubbery of the compiler (vb), I do recall spottiing such a creature in there.
Eg a type in the compiler that represents numbers.
May 18, 2014 at 4:06 AM
Upon some further consideration, my real objection is more to the behavior of hex number literals and unsigned types more so than to the type inference of constants; still, I would be somewhat concerned that in a list of type definitions like the above the need for bit31 to be long rather than UInt32 might not be nearly as apparent as in code which is performing bit masking on long values.

Otherwise, there is a suffix for unsigned, but there's none for int which would instruct the compiler to squawk if it can't fit something in an int rather than going to a larger type. What would you think, conceptually, of saying that type inference would be permitted for Int32 or double, but would not apply to larger numeric literals unless their type was implied by a suffix (if a literal is written as 0x80000000u, that would suggest the programmer was expeciting UInt32, but if it's written as 0x80000000, the programmer might have intended it to behave as either either UInt32 or Int64; given that either intention could be easily expressed, I would favor requiring the programmer to indicate the intention.
May 18, 2014 at 10:43 AM
Edited May 18, 2014 at 10:45 AM
supercat wrote:
What would you think, conceptually, of saying that type inference would be permitted for Int32 or double, but would not apply to larger numeric literals unless their type was implied by a suffix (if a literal is written as 0x80000000u, that would suggest the programmer was expeciting UInt32, but if it's written as 0x80000000, the programmer might have intended it to behave as either either UInt32 or Int64; given that either intention could be easily expressed, I would favor requiring the programmer to indicate the intention.
I think that
  • type inference should be allowed for local constants
  • the type of the constant should be inferred to be the compile time type of the initializer expression, if it has a type. Otherwise (e.g. null), type inference is not possible and the compiler emits an error
  • the rules used to determine the compile time type and value of an expression should not change because of this proposal
What you are saying, is that you don't like the current rules to determine the compile time type of an expression, particularly for hexadecimal literals. I think you do have a point there, but what you propose is a breaking change to the language, and it isn't going to happen. Also, I don't think that in practice this problem is as big as you seem to think. People that do bitwise operations know what they are doing, and they understand integral types. If type inference for local constants is allowed, that doesn't mean it's required. You can of course continue to be explicit for clarity, or when needed to get the correct type.
May 18, 2014 at 5:42 PM
Edited May 18, 2014 at 5:43 PM
It would not be a "breaking" change to the language to state that in a place where type inference is not presently permitted it would be subject to more stringent rules than type inference in other places. Actually, Eric Lippert's reasons for disallowing var fields would be just as applicable with const fields as with other fields; and I see no particular reason not to apply that same reasoning to const. Suppose a class contained the declaration:
public const var MaxSpeed = 2150000000; // Maximum number of widgets per hour
and a consumer used it thus:
 var Speed = fooClass.MaxSpeed;
 long MaxWidgetsPerDay = fooClass.MaxSpeed*24;
would you consider it more likely that the declaration would get revised to:
public const MaxSpeed = 2140000000; // Maximum number of widgets per hour
public const MaxSpeed = 2140000000L; // Maximum number of widgets per hour
I would suspect that 90% of the time a typical programmer was asked to make the change, the declaration would be rewritten in the first (incorrect) form. On the other hand, if the original declaration had been required to be written as one of these:
public const long MaxSpeed = 2150000000; // Maximum number of widgets per hour
public const var MaxSpeed = 2150000000L; // Maximum number of widgets per hour
the declaration would remain correct if the number was changed but nothing else was.
May 20, 2014 at 7:45 AM
supercat wrote:
It would not be a "breaking" change to the language to state that in a place where type inference is not presently permitted it would be subject to more stringent rules than type inference in other places.
True, but it would make the language inconsistent. The same literal that would be valid in a var declaration would not be valid in a const declaration (with type inference). No, thanks. The same literal should have the same meaning, independent of context.
May 20, 2014 at 11:26 PM
KrisVDM wrote:
True, but it would make the language inconsistent. The same literal that would be valid in a var declaration would not be valid in a const declaration (with type inference). No, thanks. The same literal should have the same meaning, independent of context.
The argument against allowing var with class members (as opposed to local varialbes), const or otherwise, is that the variable's type might not be as expected. I would suggest that it should be allowed in a few cases where the type of the expression is "obvious". For example, I would suggest that:
protected var myCat = new Cat();
protected var myCat = Cat.MethodReturningCat();
protected var myCat = (Cat)Animal.MethodReturningAnimal();
protected var myCat = (Cat)Animal.MethodReturningCat();
should all be legal (the fourth example could be better written without var, but in the other three cases var eliminates redundancy)
protected var myCat = Animal.MethodReturningCat();
should not. There's no syntactic reason why the second example should be legal and the last not, but if the only methods which can partake in field type inference are static which are invoked on their return type, then someone looking at the code would be able to readily see the type of myCat.

I believe compilers should make inferences in cases where there's only one thing a programmer could have plausibly intended, and refrain from inferences in cases where a programmer might plausibly have intended something else. Having the type of an expression change based upon the numerical value makes it very plausible that a change to the numerical value might inadvertently change the type. While simplicity and consistency in language rules is often a good thing, they should not be the only objective. When it comes to what compiler inferences should be available, slightly more sophisticated rules which consider what things could have effects other than the intended ones are often more helpful than overly simplistic rules which assume that anything which is legal in one place should be legal everyplace.
Jun 11, 2014 at 1:34 PM
I have been thinking a bit more about my proposal.

Why do people use local constants?
  • to express intent
  • for performance, as a micro-optimization to enable constant propagation and constant folding
Being able to express intent and do it concisely is important, hence my proposal.

As for the optimization side of it, the compiler could, especially when optimizations are turned on, perform those optimizations automatically when possible, even when the developer did not use the const keyword.

Now the only question is: which .NET compiler, if any, does these optimizations?

I'm guessing that .NET Native would do them, whether or not the C# compiler did them first. What about RyuJit? Or Roslyn?

The reason I ask is of course that if the compiler chain performs the optimizations automatically, one of the two reasons to use const disappears, and this proposal becomes less important (as it looses an argument in favor of it).
Jun 11, 2014 at 5:21 PM
KrisVDM wrote:
Why do people use local constants?
  • to express intent
  • for performance, as a micro-optimization to enable constant propagation and constant folding
Being able to express intent and do it concisely is important, hence my proposal.
Expressing intent, and being able to do so without repetition is a good thing. One of the reasons I favor var--and would favor it even for fields or public members--in cases where the type is "obvious" is that it not only avoids repetition in cases where the intention is to declare something with the obvious type, but also helps to suggest that what looks like a redundant specification of some humongous nested generic type might not be, and should thus be examined closely.

With regard to constants, unless facilities are added to allow certain kinds of structures to be regarded as compile-time constants [a compiler for a .NET language may regard an instance of any structure type as a compile-time constant if and only if all its fields can be resolved to compile-time constants], I don't think there would be any case in which the type of a constant would be anything more verbose than a single seven-letters token in C# (eight letters in VB.NET, which allows literals of type DateTime), and thus using var rather than a type would at best replace a 7-8 letter token with a 3-letter one.

I would consider the change to the language harmless if its usage were restricted to cases where the type is "obvious"; if public field declarations were allowed to infer types in cases where the intent was "obvious", I would see no reason not to allow such usage with constants as well.