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