Req27: Allow query syntax in For Each


[This post is part of a series, “wish-list for future versions of VB“]


 


IDEA: Allow “For Each x In collection Where x>10”, and other query expressions. We should allow this kind of code:


For Each x In collection Where x > 5



Currently we’re forced to use awkward workarounds:



For Each x In From x_ In collection Where x_ > 5


For Each x In collection.Where(Function(x_ As Integer) x_ > 5)



For Each changedEntity In entityIndex.AllEntities.Where(
            Function(x) x.ChangeTracker.State = ObjectState.Deleted)


 


IDEA 1: Allow only “Where” in For Each statements


IDEA 2: Allow arbitrary LINQ queries in For Each statements


 


This looks like a simple enough request, but it’s not… The issue is that For Each uses the Collection Pattern: it looks for an accessible method or extension method named GetEnumerator, and on that it looks for MoveNext/Current; or failing that it looks for IEnumerable(Of T); or failing that it looks for IEnumerable. Meanwhile, a LINQ query use the Query Pattern: it looks for a conforming Select method, or failing that AsEnumerable/AsQueryable, or failing that Cast.


How to deal with this “impedance mismatch”? One solution is to go for IDEA 1, i.e. we figure out up-front which limited set of LINQ-like operators we want to allow. The problem with this is that it’s so limiting, and we might get the set of operators wrong, and they wouldn’t quite behave like thier LINQ counterparts.


Another solution is to go for IDEA 2, but say that every “querying For Each” statement is merely syntactic sugar for some LINQ query, e.g. “For Each x in coll where x>5” is merely syntactic sugar for “For Each x in (From x_ in coll where x_ > 5)”.


Then we worry about performance. The compiler generates faster code when you “For Each” over an array than when you For Each over a collection. We would want “For Each x in coll Where x<5” to be exactly as performant as “For Each x in coll : If Not x<5 Then Continue”.


 


Provisional evaluation from VB team: We’re torn. It’s a nice feature. But performance concerns push us to IDEA 1 with its problems, while generality/usability concerns push us to IDEA 2 with its problems.


What do you think? Would you like to see the more limited IDEA 1? With which LINQ-like query operators? Or would you prefer to see the more general IDEA 2?


 

Comments (7)

  1. Joshua Frank says:

    I think IDEA 1 is going to be annoying when inevitably someone would want to do something obviously correct from a LINQ perspective, but is blocked because they didn’t memorize a second set of rules.

    Per IDEA 2, why is there any particular performance issue?  If you do

    For Each x in query

    I think it’s clear that the performance of this is going to be comparable to

    Dim query = <LINQ_QUERY>

    For Each x in query

    it’s just syntactic sugar.  How could the compiler possibly do better than this, if query isn’t something simple like an array?

  2. Héctor says:

    I’d prefer to see other things before this one, since I like to use the LINQ lambda syntax on these situations, however it would add some nice syntactic sugar, and would look inline with the extended switch support VB has.

  3. Kyralessa says:

    In C# I would just do this:

    foreach (var x in collection.Where(x => x > 5))

    The fact that this is a "problem" in VB is, I think, attributable to the terrible and wordy lambda syntax in VB.  If lambda expressions were as concise in VB as in C#, I don’t think it’d be a problem at all.

  4. Adam Speight says:

    Why not introduce a new Keyword for For?

    For Every x In y Where x > 10

    ' do stuff

    Next

    Now this follows the query pattern. So pass the option on the user to choose.

    Or introduce an alias for the Function keyword in Lambda Functions eg F

    (Its closer to the mathematical function symbol F)

    Or use a modified version of the C# syntax -> and  <- (Note: Not the standard operators => and <= )

  5. Adam Speight says:

    Why not introduce a new Keyword for For?

    For Every x In y Where x > 10

    ' do stuff

    Next

    Now this follows the query pattern. So pass the option on the user to choose.

    Or introduce an alias for the Function keyword in Lambda Functions eg F

    (Its closer to the mathematical function symbol F)

    Or use a modified version of the C# syntax -> and  <- (Note: Not the standard operators => and <= )

  6. Strilanc says:

    I think allowing arbitrary queries, using the current for-loop syntax, would be confusing. For example, the loop variable in "For e in collection select x = e + 1" would have to be 'x', but initially looks like it should be 'e'. I also think only allowing specific queries would be too restrictive.

    I suggest saying "For From" instead of just "For". That way it is clearer the loop is just syntactic sugar for the non-standard "ForEach" linq method. Example:

    For From x in Items1

       From y in Items2

       Select s = x + y

       Zip i in Naturals

       Print(i.ToString() + ": " + s.ToString())

    Next

  7. TA says:

    Hi Lucian,

    I propose not introducing new keywords. Instead, I propose the following

    1. If the developer wishes to support arbitrary LINQ queries using the "For Each" syntax then she should use an IQueryable as the enumeration target; e.g.,

          For Each x in collection.AsQueryable WHERE…

    2. If the enumeration target is not an IQueryable then the compiler should issue an error if LINQ syntax is used, or provide a squiggle line under the enumerable object to enable the developer add the AsQueryable extension method

    Thoughts?

    TA