Search code examples
f#computation-expressionquery-expressions

How do you compose query expressions in F#?


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


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")