Language Idea: Queryable Iterators #7638
Unanswered
AdamSobieski
asked this question in
Language Ideas
Replies: 1 comment 17 replies
-
What optimizations would this enable. Generally speaking, we don't do language features based on speculation that there will be performance. Rather, we want to see that performance is lacking, and has no way to obtain, and then we design with a drive to solve that performance issue. This allows us to actually validate the language/solution, and not end up with something that doesn't actually work in practice. |
Beta Was this translation helpful? Give feedback.
17 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Introduction
Hello. I would like to share and to flesh out some ideas pertaining to an interesting C# language idea: queryable iterators.
C# Iterators
Quoting from this article, we can observe that an iterator function resembling:
can be compiled into something like (metadata attributes stripped for brevity):
Queryable Iterators
What if developers could express, in hopes of enabling runtime optimizations, something like:
In this case, the resultant compiled code could be such so that the private sealed class
<GetCounter>d__0
would also implementIQueryable<int>
and, thus,IQueryable
.This would entail that the class
<GetCounter>d__0
would have propertiesElementType
,Expression
, andProvider
.The matter of implementing
ElementType
would be simple. For the example, it would returntypeof(int)
.For the
Provider
property, a resulting private sealed class<GetCounter>d__0
could, if its privateIQueryProvider
member, e.g.,<>n__provider
, werenull
, activate one using a runtime-default provider type, caching the value. In theory, .NET could provide a default query provider for queryable iterators. As described below, developers could provide custom ones or use third-party ones.The remaining important matter for consideration, then, would be the nature of the expression tree returned by the
Expression
property. It could represent: (1) the iterator method before the compiler-time transformation, (2) anInvocationExpression
which invokes a usefulLambdaExpression
, (3) aMethodCallExpression
expression, (4) another, perhaps more intricate, technique.Customizable Query Providers
Developers using queryable iterators might want to provide their own
IQueryProvider
implementations or to make use of third-party ones. This could be achieved with metadata:That metadata attribute, or another identically initialized attribute, could be placed on the resulting private sealed class
<GetCounter>d__0
. The<GetCounter>d__0
class could, for itsProvider
property, if a private backing field,<>n__provider
, werenull
, activate an instance of that type obtained from itsQueryProviderAttribute
metadata attribute and assign it to that backing field member, caching the value.Use Case: C# Logic Programming
Optimizations needn't entirely focus on the streaming outputs from the iteration (see: LINQ). Developers could also perform operations and processing upon input arguments provided to iterator functions. This is the case with some approaches to logic programming in C#, e.g., YieldProlog (YP).
While perhaps beyond the scope of this discussion about queryable iterators as a language idea, the YP C# logic programs
unify
variables as iterators are iterated, searching for solutions. Outputs can be provided in the form of those instantaneous values on the variables passed to the iterator functions as arguments. Each time thatMoveNext()
is called on the resultant enumerator, the variables could have different values.This is a different scenario than that of streaming output values (see: LINQ) but is also one that a third-party library provider might find interesting to provide optimizations for.
Use Case: LINQ-related Runtime Optimizations
Enumerables
Optimizations are possible with respect to queryable iterators and LINQ operators. Let us consider an optimization-related extension method,
Optimize<T>()
, resembling the following:Let us also consider the following two queryable iterator functions:
and
With the following LINQ:
a resultant queryable iterator function,
Function3
, might resemble:or, if the optimizer detected this edge case,
Function3
might resemble:Next, utilizing the
Where
operator:a resultant queryable iterator function,
Function4
, might resemble:Similarly, with a combined
Where
andSelect
operator:a resultant queryable iterator function,
Function5
, might resemble:For a more complex LINQ example, let us add another function:
And a LINQ expression:
a resultant queryable iterator function,
Function7
, might resemble:With
LambdaExpression
s available for queryable iterator functions, runtime compilers could produce resultant queryable iterator functions which optimize for, in some cases inlining, those lambda expressions provided for subsequently utilized LINQ operators including, but not limited to,Concat
,Where
,Select
, andSelectMany
.Non-enumerables
Beyond those LINQ operators like
Concat
,Where
,Select
, andSelectMany
, indicated above, one might wonder about those LINQ operators likeAll
,Any
, andCount
?In these regards, we might consider
QueryableValue<T>
andQueryableCachingValue<T>
, classes with which to express "IQueryable
for non-enumerables". These would provide non-caching and caching access to object values while also providingElementType
,Provider
, andExpression
properties.As envisioned,
QueryableValue<T>
would consult its backing method each time its value was requested andQueryableCachingValue<T>
would cache its value after it was first accessed.With classes resembling those, "
IQueryable
for non-enumerables", theOptimize()
pattern could be expanded to include more of the LINQ operators.That is, something like:
could result in a function resembling
Function8
, below, being invoked whenever theValue
is desired:or only when first desired:
Potential Complexities
Some complexities may arise from the LINQ AST not supporting expressiveness for all of the newest C# language features.
The simplest approach to having an inspectable expression tree for a queryable iterator is to use an
InvocationExpression
upon aLambdaExpression
which represents the program logic of the pre-compiler-transformed queryable iterator. However, as it is an iterator, it utilizesyield return
, and there is not aYieldReturnExpression
in the LINQ AST. Accordingly, other approaches can be explored.Other Uses and Related Scenarios
Beyond developers hoping to optimize queryable iterator functions at runtime with LINQ, queryable iterators would be a convenient way to easily create useful
IQueryable<T>
instances.Other, related scenarios include considering return types of
IObservable<T>
and, in particular,IQbservable<T>
as well as mappings betweenIEnumerable<T>
andIObservable<T>
andIQueryable<T>
andIQbservable<T>
. These mappings would be desired to work and to be interoperable with queryable iterators.Conclusion
Thank you.
Beta Was this translation helpful? Give feedback.
All reactions