csharplang/meetings/2013/LDM-2013-12-16.md
2017-02-09 09:25:09 -08:00

6.8 KiB
Raw Permalink Blame History

C# Design Notes for Dec 16, 2013

Agenda

This being the last design meeting of the year, we focused on firming up some of the features that wed like to see prototyped first, so that developers can get going on implementing them.

  1. Declaration expressions <reaffirmed scope rules, clarified variable introduction>
  2. Semicolon operator <reaffirmed enclosing parentheses>
  3. Lightweight dynamic member access <decided on a syntax>

Declaration expressions

On Sep 23 we tentatively decided on rules for what the scope is when a variable is introduced by a declaration expression. Roughly speaking, the rules put scope boundaries around “structured statements”. While this mostly makes sense, there is one idiom, the “guard clause” pattern, that falls a little short here:

if (!int.TryParse(s, out int i)) return false; // or throw, etc.
 // code ideally consuming i

Since the variable i would be scoped to the if statement, using a declaration statement to introduce it inside of the if would not work.

We talked again about whether to rethink the scope rules. Should we have a different rule to the effect essentially of the variable declaration being introduced “on the preceding statement”? I.e. the above would be equivalent to

int i;
if (!int.TryParse(s, out i)) return false; // or throw, etc.
 // code consuming i

This opens its own can of worms. Not every statement has a preceding statement (e.g. when being nested in another statement). Should it bubble up to the enclosing statement recursively? Should it introduce a block around the current statement to hold the generated preceding statement, etc.

More damning to this idea is the issue of initialization. If a variable with an initializer introduced in e.g. a while loop body is understood to really be a single variable declared outside of the loop, then that same variable would be re-initialized every time around the loop. That seems quite counterintuitive.

This brings us to the related issue about variable lifetimes. When a variable is introduced somewhere in a loop, is it a fresh variable each time around? It would probably seem strange if it werent. In fact we took a slight breaking change in C# 5.0 in order to make that the case for the loop variable in foreach, because the alternative didnt make sense to people, and tripped them up when they were capturing the variable in lambdas, etc.

Conclusion

We keep the scope rules described earlier, despite the fact that the guard clause example doesnt work. Well look at feedback on the implementation and see if we need to adjust. On variable lifetimes, each iteration of a loop will introduce its own instance of a variable introduced in the scope of that loop. The only exception is if it occurs in the initializer of a for loop, because that part is evaluated only on entry to the loop.

Semicolon operator

We previously decided that a sequence of expressions separated by semicolons need to be enclosed in parentheses. This is so that we dont get into situations where e.g. commas and semicolons are interspersed among expressions and it isnt visually clear what the precedence is.

We discussed briefly whether those parentheses are necessary even when there is other bracketing around the semicolon-separated expressions.

Conclusion

Its not worth having special rules for this. Instead, semicolons are a relatively straightforward addition to parenthesized expressions something like this:

parenthesized-expression:

  • ( expression-sequence_opt expression_ )

expression-sequence:

  • expression-sequence_opt statement-expression ;
  • expression-sequence_opt declaration-expression ;

Lightweight dynamic member access

On Nov 4 we decided that lightweight member access should take the form x.<glyph>Foo for some value of <glyph>. One candidate for the glyph is #, so that member access would look like this:

payload.#People[i].#Name

However, # plays really badly with preprocessor directives. It seems almost impossible to come up with syntactic rules that reconcile with the deep lexer-based recognition of #-based preprocessor directives. After searching the keyboard for a while, we settled on $ as a good candidate. It sort of invokes a “string” aspect in a tip of the hat to classic Basic:

payload.$People[i].$Name

One key aspect of dynamicness that we havent captured so far is the ability to declaratively construct an object with “lightweight dynamic members”, i.e. with entries accessible with string keys. A core syntactic insight here is to think of $Foo above as a unit as a “lightweight dynamic member name”. What this enables is to think of object construction in terms of object initializers where the member names are dynamic:

var json = new JsonObject 
{ 
    $foo = 1,
    $bar = new JsonArray { "Hello", "World" },
    $baz = new JsonObject { $x = 1, $y = 2 }
};

We could also consider allowing a free standing $Foo in analogy with a free standing simple name being able to reference an enclosing member in the implicit meaning of this.$Foo. This seems a little over the top, so we wont do that for now. One thing to consider is that objects will sometimes have keys that arent identifiers. This is quite common in Json payloads. Thats fine as far as member access is concerned: just fall back to indexing syntax when necessary:

payload.$People[i].["first name"]

It would be nice to also be able to initialize such “members” in object initializers. This leads to the idea of a generalized “dictionary initializer” syntax in object initializers:

var payload = new JsonObject
{
    ["first name"] = "Donald",
    ["last name"] = "Duck",
    $city = "Duckburg" // equivalent to ["city"] = "Duckburg"
};

So just as x.$Foo is a shorthand for x["Foo"] in expressions, $Foo=value is shorthand for ["Foo"]=value in object initializers. That syntax in turn is a generalized dictionary initializer syntax, that lets you index and assign on the newly created object, so that the above is equivalent to

var __tmp = new JsonObject();
__tmp["first name"] = "Donald";
__tmp["last name"] = "Duck";
__tmp["city"] = "Duckburg";
var payload = __tmp;

It could be used equally well with non-string indexers. A remaining nuisance is having to write all the type names during construction. Could some of them be inferred, or a default be chosen? Thats a topic for a later time.

Conclusion

Well introduce a notion of dictionary initializers [index]=value, which are indices enclosed in […] being assigned to in object initializers. Well also introduce a shorthand x.$Foo for indexing with strings x["Foo"], and a shorthand $Foo=value for a string dictionary initializer ["Foo"]=value.