Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: as-based declaration expressions #402

Closed
DavidArno opened this issue Apr 4, 2017 · 12 comments
Closed

Proposal: as-based declaration expressions #402

DavidArno opened this issue Apr 4, 2017 · 12 comments

Comments

@DavidArno
Copy link

DavidArno commented Apr 4, 2017

EDIT - I have removed references to patterns as calling this an "as pattern" doesn't help with conveying its purpose.

With C# 7, we now have a new x is var y construct, that allows a new variable to be introduced within an expression, for later use in that expression:

public override bool Equals(object obj) => 
    obj is Success<T> other && 
    other._hasError == _hasError &&
    (_hasError && other.Error.Equals(_error) || !_hasError);

This works great in situations where a bool result is required from the expression. However, sometimes the result of the exression needs to be the variable's type, not bool. With reference to Declarations within object initialisers, this could be achieved by allowing assignment statements to be used in expressions, such as in the following example:

// Current syntax:
public SomeType F()
{
    var intermediate = GetSomeValue();
    SomeMethod(intermediate.P1, intermediate.P2)
}

// could be written as
public SomeType F() =>
    SomeMethod((var intermediate = GetSomeValue()).P1, intermediate.P2)

However, as alternative would be to copy the style of x is T y expression, by introducing a new x as T y expression:

public SomeType F() =>
    SomeMethod((GetSomeValue() as var intermediate).P1, intermediate.P2)

This could then have the same scoping rules as is and work in a near-identical fashion, save that the result of is of type T, rather than bool.

Update 18th May 2017
@mattwar's alternative proposal for variable declarations in expressions has highlighted more use-cases for using as in this way.

Rather than having to declare a variable in advance and then use it in multiple tests in an if or while:

char ch;
while ((ch = GetNextChar()) == 'a' || ch == 'b' || ch == 'c') 
{
}

x as var y could be used to introduce a variable in the first test, allowing it's re-use in subsequent ones:

while ((GetNextChar() as var ch) == 'a' || ch == 'b' || ch == 'c') 
{
}

Also, when combined with recursive pattern matching, it could be used create a pattern that allows both testing of the innards of a tuple as well as introducing a variable of that tuple type:

// for object, o
switch (o)
{
    case (1, var a) as t when MeetsCondition(a): 
        HandleTuple(t);
        break;
    ...
}
@HaloFour
Copy link
Contributor

HaloFour commented Apr 4, 2017

Why is this an improvement? The only benefit I see is that it lets you cram more onto a single line expression. But I find the "current syntax" version to be significantly easier to read.

@DavidArno
Copy link
Author

@HaloFour,

It's an improvement in the same way that x is T y (and out var for that matter) are improvements: if the variable is only used in the expression, then defining within the scope of that expression clarifies the intent. An example of this is in my original suggestion around this topic:

var pointMass1 = new PointMass(mass: 1.0f);
var pointMass2 = new PointMass(mass: 2.0f);
var simulation = new Simulation
{
    pointMass1,
    pointMass2,
    new Spring(pointMass1, pointMass2)
};

versus

var simulation = new Simulation
{
    new PointMass(mass: 1.0f) as var pointMass1,
    new PointMass(mass: 1.0f) as var pointMass2,
    new Spring(pointMass1, pointMass2)
};

@HaloFour
Copy link
Contributor

HaloFour commented Apr 4, 2017

This is less a pattern unto itself and more the use of patterns with the as operator. The pattern is still var. Which forces the question as to how this operator would work with other patterns, especially conditional or recursive patterns:

SomeObjectReturningFunction() as string text;

Is text definitely assigned there? If so, what is it's value, null?

SomeObjectReturningFunction() as int number;

What about here? Should that even be legal or should number have to be an int? instead to permit nullability?

And what about:

SomeObjectReturningFunction() as Point(var x, var y);

If the function doesn't return a Point (assuming that Point can be deconstructed) what are the values of x and y?

And in all of these cases is the type of the expression still object?

I'd assume that these pattern variables would have the same scoping rules as is patterns used in that context, despite either of our objections to that.

@svick
Copy link
Contributor

svick commented Apr 4, 2017

@HaloFour I think that the least confusing approach, as long as the new operator is called as, is to make it behave as close as possible to the existing as. That is:

  • The type has to be nullable (either a reference type or nullable value type).
  • If the type doesn't mach, null is returned.
  • The type of the expression is the specified type (e.g. string in your first example).

@DavidArno
Copy link
Author

@HaloFour
My thoughts on your excellent points:

SomeObjectReturningFunction() as string text; // text will be null if the returned value is 
                                              // not a string. text is type string
SomeObjectReturningFunction() as int number; // won't compile (same as 
                                             // existing "as")
SomeObjectReturningFunction() as int? number; // would work fine
SomeIntReturningFunction() as int number; // would work fine

struct ValuePoint ... // defining double X and Y with Deconstruct
class ReferencePoint ... // defining double X and Y with Deconstruct

SomeObjectReturningFunction() as ValuePoint(var x, var y); // won't compile (same as
                                                           // existing "as")
SomeValuePointReturningFunction() as ValuePoint(var x, var y); // works fine

SomeObjectReturningFunction() as ReferencePoint(var x, var y); // either won't compile, or 
                                                               // x & y will be double? and
                                                               // null if result isn't
                                                               // ReferencePoint

I'd assume that these pattern variables would have the same scoping rules as is patterns used in that context, despite either of our objections to that.

I think it would have to, unfortunately.

@sedlacl
Copy link

sedlacl commented Apr 12, 2017

I do not want to be a grouch, but I can't see any income from this pattern except worse readability of code.

@DavidArno
Copy link
Author

DavidArno commented Apr 12, 2017

@sedlacl,

Out of curiosity, do you have the same view for the x is T y pattern too? I'm struggling to understand how anyone can view it as worse readability as it's more readable in my view. Different approaches, I guess.

@sedlacl
Copy link

sedlacl commented Apr 12, 2017

@DavidArno ,

To be honest, x is T y pattern is wierd too.
I think the base syntax of as is better

var y= x as T; 
y?.DoSomething();

But on the other side I have to say I new switch syntax - it helps readability of code.

@DavidArno
Copy link
Author

@sedlacl,

Then it is a case of very different ways of coding (and views on what's readable). In my view, the ?. operator is just about as ugly as code can get. I'd rewrite that as:

if (x is T y) y.DoSomething();

switch is now better, but it's still a clunky, syntax-heavy statement. I've gone from almost never using it, to now sometimes using it. Still hanging out for the match expression though, which will be genuinely useful to me.

@jnm2
Copy link
Contributor

jnm2 commented Apr 12, 2017

Since I saw this issue and started paying attention, I've wanted this feature about twice a day while coding. It feels natural beside is.

@sedlacl
Copy link

sedlacl commented Apr 12, 2017

@DavidArno ,
I see your point. Generally I don't like declarations in nested statements and conditions, but is my personal point of view.

?. operator has sense when you have nested statements like x?.y?.z?.DoSomething(). In my previous case is generally as ugly as your if (x is T y) y.DoSomething().

I think the ugly part is that you have outer-scope declaration in nested statement.

If it looks like:

if (x is T y) y.DoSomething();
y.DoSomething(); //will fail - y is unknown

it would be clear and easy to read, but it isn't:) So I have to say, that your as pattern isn't probably much worser.

@alrz
Copy link
Member

alrz commented May 18, 2017

I think there is some confusion in this proposal regarding "as" patterns, syntactically it would be defined under "pattern" rules:

pattern:
as-pattern

as-pattern:
pattern as identifier

This is how F# as-patterns are defined.

So, that is not to say that we extend the existing as operator to accept patterns. We're just using that token to assign a name to a succeeded pattern output e.g. a tuple pattern produces a tuple, etc.

Furthermore, as itself probably can't be used here because it has the same precedence of is operator with left associativity, for example, the following is currently valid syntax and would be ambigious under this proposal.

if (o is Identifier as Identifier)

And by the way, this exists in current spec draft: the identifier following a complex pattern:

if (x is (1, int a) t)
if (x is P(int a) p)

Usage of as was mentioned in #277 to ease unification of patterns and expressions.

@DavidArno DavidArno reopened this Sep 12, 2017
@DavidArno DavidArno changed the title Proposal: as pattern Proposal: as-based declaration expressions Oct 6, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants