Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constructing computation expression programmatically

Tags:

f#

I'm want to create a method which would construct an equivalent of query { ... } computation expression (using FSharp.Data.TypeProviders with either LINQ2SQL or LINQ2Entities) from provided data structure, i.e.:

Collection("customers", [ 
    Field "customerId"; 
    Collection("orders", [ 
        Field "orderId" ]) ])

For that I need to understand how the query is translated into code on the first place. Given following query as an example:

query {
    for c in db.Customers do
    select (c.CustomerID, query {
        for o in c.Orders do
        select o.OrderID
    } |> Seq.toList)
} 

What would it look like without computation expression syntax?

like image 549
Bartosz Sypytkowski Avatar asked Jul 27 '26 14:07

Bartosz Sypytkowski


2 Answers

I would not recommend generating F# queries programmatically.

The main purpose of queries is to make it possible to write nicely looking F# source code. When they are executed, the code you write is first translated to LINQ-style expression tree, which is then translated to SQL query. If you managed to translate your query specification into F# query, you'd have something like:

 +------------+    +----------+    +----------------------+    +-----+
 | Your query | -> | F# query | -> | LINQ expression tree | -> | SQL |
 +------------+    +----------+    +----------------------+    +-----+

There is nothing wrong with multiple transformations, but there is a number of things that make the path via F# query more complicated:

  • In your query format, things seem to be represented as strings. So, you'd need to go from strings to .NET MethodInfo just to go back to strings.

  • With F# queries/LINQ, you get back typed objects and that's one of the main benefits, but if you build query dynamically, you'll just get back a squence of obj values - and will have to use reflection or something like that to access them.

TL;DR - I think it will be much easier to just take your query representation and generate SQL directly. The structure of the transformation is similar to generating F# query, but you won't have to mess around with quotations and reflection.

like image 121
Tomas Petricek Avatar answered Jul 30 '26 04:07

Tomas Petricek


If you want to see how something desugars, just put it into a quotation. If you do this with your example, you'll see that

<@ 
    query {
        for c in db.Customers do
        select (c.CustomerID, query {
            for o in c.Orders do
            select o.OrderID
        } |> Seq.toList)
    } 
@>

is roughly equivalent to

query.Run 
    <@ query.Select(query.For(query.Source db.Customers, 
                              fun c -> query.Yield c), 
                    fun c -> 
                        c.CustomerID, 
                        query.Run <@ 
                                    query.Select(query.For(query.Source c.Orders, 
                                                           fun o -> query.Yield o), 
                                                 fun o -> o.OrderID) @> 
                             |> Seq.toList) @>

(where I've cleaned up a few extraneous variables). I agree with Tomas that there are probably better ways to achieve what you want than trying to create such an expression directly.

like image 35
kvb Avatar answered Jul 30 '26 04:07

kvb