Query expression ( Query Design continued ):

This post is a part of a series of posts about query design. For the previous post see: https://blogs.msdn.com/vladimirsadov/archive/2007/01/30/representing-the-user-code-by-delegates-query-design-continued.aspx

 

In the previous posts we have designed a very handy function that can iterate any IEnumerable(of T) and apply given expression to all elements, but the disadvantages are obvious – more code to type. We tried to reduce the amount of actual code that user writes, but now we require that:

1) User code is placed into a function.

2) Instance of a delegate representing user code needs to be created and passed into the function.

3) User needs to specify type arguments for the delegate

 

The good part is that these actions are extremely mechanical. They are so mechanical, that they can be done by the compiler itself. We just need a new language construct that will take only essential information from the user. The essential information that is needed here is the source collection and the expression that is applied to elements. In addition to this we need to specify the name for the current element so that we could refer to it inside the expression.

 

Dim result = <KEYWORD> current_number <SEPARATOR> numbers <SEPARATOR> expression

 

Interestingly enough this is very similar to SELECT statement in SQL. This is not a coincidence though as the SQL query basically performs the same action of projecting elements of one table into another using user specified expression.

 

SELECT expression FROM current_number IN numbers

 

So it should not be surprising that VB syntax for query expressions uses same keywords as in SQL. In fact the initial syntax for VB queries was almost identical to SQL. However it was discovered that there are many advantages such as better intellisense when iteration variable (current_number) is already declared by the time when user starts typing the expression. As a result the syntax was revised and From clause was brought to the beginning of the query expression.

 

Example:

 

' query that gets number names

Dim num_names = From cur_num In numbers Select result = cur_num.ToString

 

The code clearly describes what we want – we want to get all elements in “numbers” collection and generate a sequence of “result” values that are produced by applying “ToString()” function to each element. Based on the query, compiler can extract the expression that we specified in Select clause and generate a synthetic function that performs this action.

 

' translation of the query

Dim num_names = IterateAndApply(numbers, AddressOf CompilerGeneratedFunction)

' =====================================

' synthetic function that is automatically generated by the compiler

' it performs the actions specified in Select clause

'======================================

Function CompilerGeneratedFunction(ByVal cur_num As Integer) As String

        Dim result = cur_num.ToString

        Return result

End Function

This is much better. It takes only one line to produce a sequence of number names. The rest of the code is either compiler generated or in a library. In order to fully appreciate the convenience, let’s consider another example. What if instead of strings we need to produce a sequence of squares?

 

Code:

 

Dim num_squares = From cur_num In numbers Select result = cur_num * cur_num

 

This is it!! This is all the code that you need to write!!!

 

Compiler can take care of generating a function that will perform the Select expression (also called projection). The rest of what is needed (iterating the source collection, applying the projection and forming the result sequence) did not really change and the same library functions can be reused.

 

No more duplication of code. No more off-by-one bugs and out-of-range exceptions. All we need is to make sure that the projection does what we wanted. Reusability rules!!!