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

Champion "Permit expression variables everywhere" #1020

Closed
5 tasks
gafter opened this issue Oct 20, 2017 · 11 comments
Closed
5 tasks

Champion "Permit expression variables everywhere" #1020

gafter opened this issue Oct 20, 2017 · 11 comments
Assignees
Milestone

Comments

@gafter
Copy link
Member

gafter commented Oct 20, 2017

  • Proposal added
  • Discussed in LDM
  • Decision in LDM
  • Finalized (done, rejected, inactive)
  • Spec'ed

There are three contexts where out variable declarations and pattern matching variable declarations are not permitted today, in part because we did not decide what the scope should be. I propose we decide for each of these three contexts, and extend the language to permit declaration expressions in these contexts.

  1. Constructor-initializers. The primary question is whether the scope of variables declared in a ctor-initializer extends to the constructor body or not. I propose that it should extend to the body.

  2. Field initializers. The primary question is whether the scope of variables declared in the one equals-value-clause is in scope in subsequent equals-value-clauses of the same field declaration (when it declares more than one field). It is also possible to extend the scope to subsequent separate field declaration's initializers too, but I don't think that is likely to be approved by the LDM.

  3. Query clauses. The primary question is what is the scope of variables declared in a query clause that is specified to be translated into the body of a lambda. One possible resolution is that its scope is just that expression of the clause.

@gafter gafter added this to the 7.X candidate milestone Oct 20, 2017
@gafter gafter self-assigned this Oct 20, 2017
@HaloFour
Copy link
Contributor

HaloFour commented Oct 20, 2017

E.g.:

  1. public class Foo {
        public bool Success { get; }
        protected Foo(bool success) => Success = success;
    }
    
    public class Bar : Foo {
        public int Value { get; }
        public Bar(string text) : base(int.TryParse(text, out int value)) => Value = value;
    }
  2. public class Foo {
        private readonly int _field1 = int.TryParse(SomeStatic.Text, out int value) ? value : -1;
    
        // but probably not also in scope here
        private readonly int _field2 = value;
    }
  3. string[] strings = GetStrings();
    var query1 = from text in strings
        where int.TryParse(text, out int value)
        select value;
    
    int[] results = query1.ToArray();

@orthoxerox
Copy link

@HaloFour there's a missing paren in the first example

@alrz
Copy link
Member

alrz commented Oct 20, 2017

var q =
    from item in list
    where item is int value
    select value;

@HaloFour
Copy link
Contributor

@orthoxerox Fixed. Thanks.

@HaloFour
Copy link
Contributor

  1. Query clauses. The primary question is what is the scope of variables declared in a query clause that is specified to be translated into the body of a lambda. One possible resolution is that its scope is just that expression.

Please don't limit expression variables like this in LINQ. It's my opinion that the intuitive scope for variable expressions in a LINQ query would be the same for range variables.

For out declaration variables I think the translation is straightforward. Emit a call to Select prior which projects the result of the expression and any introduced variables into an anonymous type or tuple:

var query1 = from text in strings
    where int.TryParse(text, out int value)
    select value;

// translated into
var query1 = strings
    .Select(text => new {
        text = text,
        temp = int.TryParse(text, out int value),
        value = value
    })
    .Where(projected => projected.temp)
    .Select(projected => projected.value);

For patterns it gets a little trickier only because the current definite assignment rules wouldn't allow the translation to compile unless the compiler made special considerations for the where query clause:

var q =
    from item in list
    where item is int value
    select value;

// translated into
var q = list
    .Select(item => new {
        item = item,
        temp = item is int value,
        value = value //CS0165
    }) 
    .Where(projected => projected.temp)
    .Select(projected => projected.value);

There is the valid concern that implicitly allowing for these expression variables to escape the scope of the expression would produce even slower queries due to the additional calls to Select required. If the promotion of these expression variables to range variables was limited to the let clause that would alleviate that concern since that wouldn't result in any additional calls to Select but it would be an unfortunate limitation.

@gafter
Copy link
Member Author

gafter commented Oct 20, 2017

It's my opinion that the intuitive scope for variable expressions in a LINQ query would be the same for range variables.

That almost works for a few kinds of query clauses. Even if it worked for all of them, for many users it would be an unacceptable performance penalty for something they do not want or need in their code.

We intend to permit programmers to explicitly express their intent here, rather than automatically extending the scope and creating unwanted range variables by inserting implicit query clauses. See #189 . We'll do that by extending the let clause to support multiple variables:

from s in strings
let (isNumber, value) = (int.TryParse(s, out var v), v)
where isNumber
select value

@HaloFour
Copy link
Contributor

@gafter

A little kludgy but at least it would enable that kind of functionality. For pattern matching scenarios I guess the developer would have to do the following:

var q = 
    from item in list
    let (success, value) = item is int value ? (true, value) : (false, 0)
    where success
    select value;

What about resurrecting dotnet/roslyn#6400 and dotnet/roslyn#6877 to enable pattern-based destructuring in LINQ queries via the let clause? 😄

var q =
    from item in list
    let int value = item else continue
    select value;

@svick
Copy link
Contributor

svick commented Oct 21, 2017

@gafter

Even if it worked for all of them, for many users it would be an unacceptable performance penalty for something they do not want or need in their code.

Would it be possible to add the extra Select only when necessary (i.e. when the variable is actually used after the clause in which it was declared)? That way, users who care about the performance penalty would be able to write the code the way you propose. And the rest of us would get simple, clear and readable code.

@gafter
Copy link
Member Author

gafter commented Oct 21, 2017

@svick I don't know whether it is possible because I haven't heard what people think should occur with any other than one or two kinds of clauses. I suppose we are all left to imagine how the others should behave. If it were possible, it would be much more complicated to specify, implement, and reason about than the current simple syntactic translation.

@alrz
Copy link
Member

alrz commented Oct 21, 2017

for many users it would be an unacceptable performance penalty for something they do not want or need in their code.

How performance is a concern here? In that case, I think they wouldn't want to use linq in the first place (or use rewriters to eliminate allocations). I thought linq is all about syntactic transformation for the sake readability at the cost of a little magic and sometimes perf penalty?

@MadsTorgersen MadsTorgersen modified the milestones: 7.X candidate, 7.3 candidate Oct 25, 2017
@gafter
Copy link
Member Author

gafter commented Oct 25, 2017

Closing in preference of #32

@gafter gafter closed this as completed Oct 25, 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

6 participants