fsprojects / FSharp.Linq.ComposableQuery

Compositional Query Framework for F# Queries, based on "A Practical Theory of Language-Integrated Query"
http://fsprojects.github.io/FSharp.Linq.ComposableQuery/
MIT License
67 stars 13 forks source link

Incomplete normalisation due to extra terms #3

Closed ixtreon closed 10 years ago

ixtreon commented 10 years ago

Terms of the form RunQueryAsValue(Quote(e)) are not properly handled/ignored in our internal format, causing the library to fail in fully normalising queries.

We should either remove or hide such terms while translating so as to not prevent the the normalisation procedure from working across the boundaries of such calls. The former may not be possible (the attempted fix in the manner of #1 did not work) and the second may be infeasible due to the large amount of cases to handle.

As this issue does not seem to impair significantly the performance of queries with nested data (or any queries whatsoever) I am marking it as an enhancement.

ixtreon commented 10 years ago

The term e in the pattern RunQueryAsValue(Quote(e)) should always have a base return type. Since a query builder operates on the wrapped type IQuerySource<'T,'Q>, the final method call must transform its input to another type 'a which is either a value type or (possibly a wrapping of) 'T itself.

Such operations include Exists, Last, Find, MaxByNullable. They differ to all other supported methods in that they do not return a wrapped object (exceptions are Run and Source). As such these methods should be only called once in a query and they must be at its end, too. In practice, observe that queries make use of these methods only as the last method to convert a collection of values to a single value.

Additionally all supported constructs in the FSharpComposableQuery library (and the underlying T-Linq language) represent methods that return a collection (wrapped) type. The only exception is the T-Linq method Exists() which is not implemented in practice, as it does not have an equivalent in LINQ and is roughly the same as Exists(fun _ -> true).

This means we do not currently have normalisation rules that may get blocked by the RunQueryAsValue term. Nonetheless I've added a type of UnknownCall which specifies that the given call should be wrapped in a RunAsValue(Quote(_)) term. It is also important that if we start recognising those operations at some point in the future, we take care to wrap them with RunQsValue(Quote(_)) when translating back to FSharpExpr.