How do you compose query expressions in F#?

computation-expression, f#, query-expressions

Solution

This was quite easy to do with the F# 2.0 version of queries which required explicit quotations (I wrote a blog post about it). There is a way to achieve similar thing in C# (another blog post) and I think similar tricks could be played with F# 3.0.

If you do not mind uglier syntax, then you can use explicit quotations in F# 3.0 too. When you write `query { .. }` the compiler actually generates something like:

query.Run(<@ ... @>)

where the code inside `<@ .. @>` is quoted F# code - that is, code stored in an `Expr` type that represents the source code and can be translated to LINQ expressions and thus to SQL.

Here is an example that I tested with the `SqlDataConnection` type provider:

let db = Nwind.GetDataContext()

let predicate = <@ fun (p:Nwind.ServiceTypes.Products) -> 
  p.UnitPrice.Value > 50.0M @>

let test () =
  <@ query.Select
      ( query.Where(query.Source(db.Products), %predicate), 
        fun p -> p.ProductName) @>
  |> query.Run
  |> Seq.iter (printfn "%s")

The key trick is that, when you use explicit quotations (using `<@ .. @>`) you can use the `%` operator for quotation slicing. This means that the quotation of `predicate` is put into the quotation of the query (in `test`) in place where you write `%predicate`.

The code is quite ugly compared to the nice query expression, but I suspect you could make it nicer by writing some DSL on top of this or by pre-processing the quotation.

EDIT: With a bit more effort, it is actually possible to use the `query { .. }` syntax again. You can quote the entire query expression and write `<@ query { .. } @>` - this will not directly work, but you can then take the quotation and extract the actual body of the query and pass it to `query.Run` directly. Here is a sample that works for the above example:

open System.Linq
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns

let runQuery (q:Expr<IQueryable<'T>>) = 
  match q with
  | Application(Lambda(builder, Call(Some builder2, miRun, [Quote body])), queryObj) ->
      query.Run(Expr.Cast<Microsoft.FSharp.Linq.QuerySource<'T, IQueryable>>(body))
  | _ -> failwith "Wrong argument"

let test () =
  <@ query { for p in db.Products do
             where ((%predicate) p)
             select p.ProductName } @>
  |> runQuery
  |> Seq.iter (printfn "%s")

Problem

I've been looking at query expressions here http://msdn.microsoft.com/en-us/library/vstudio/hh225374.aspx And I've been wondering why the following is legitimate ``` let testQuery = query { for number in netflix.Titles do where (number.Name.Contains("Test")) } ``` But you can't really do something like this ``` let christmasPredicate = fun (x:Catalog.ServiceTypes.Title) -> x.Name.Contains("Christmas") let testQuery = query { for number in netflix.Titles do where christmasPredicate } ``` Surely F# allows composability like this so you can reuse a predicate?? What if I wanted Christmas titles combined with another predicate like before a specific date? I have to copy and paste my entire query? C# is completely unlike this and has several ways to build and combine predicates

Original source