views:

80

answers:

2

i'm writing an F# dsl for SQL (http://github.com/kolosy/furious). A select statement would look like this:

type person = {
    personId: string
    firstname: string
    lastname: string
    homeAddress: address
    workAddress: address
    altAddresses: address seq
}
and address = {
    addressId: string
    street1: string
    zip: string
}

let (neighbor: person seq) = 
    db.Yield <@ Seq.filter (fun p -> p.homeAddress.zip = '60614') @>

the obvious (and silly) question is... how do i parametrize the quotation? if i just somehting like

let z = "60614"
let (neighbor: person seq) = 
    db.Yield <@ Seq.filter (fun p -> p.homeAddress.zip = z) @>

then z gets resolved into a static property accessor (PropertyGet(None, String z, [])). I need something that will let me retrieve the value of the variable/let binding based solely on the quotation. idears?

+5  A: 

Quotations are not my forte, but check out the difference here:

let z = "60614" 
let foo = <@ List.filter (fun s -> s = z) @> 
printfn "%A" foo

let foo2 = 
    let z = z
    <@ List.filter (fun s -> s = z) @> 
printfn "%A" foo2

I think maybe having 'z' be local to the expression means the value is captured, rather than a property reference.

Brian
whoa. neat, thanks. how stable is that going to be? as in, is that an undocumented feature, or just the behaviour of the current iteration? i don't get a lot of warm and fuzzies digging through the quotation stuff.
kolosy
I believe it's a stable feature; names declared at the top level are values in a module (properties of a static class), whereas local names are just values.
Brian
d'oh. ok, so it was an issue only because of how my test harness was written. makes sense.
kolosy
+3  A: 

In addition to what Brian wrote - I believe that the encoding of access to global let bound values is also pretty stable and they will quite likely continue to be encoded as PropGet in the future.

This means that you could support this case explicitly in your translator and add a simple pre-processing step to get values of these properties. This can be done using ExprShape (which allows you to fully traverse quotation just using 4 cases). This would allow your DSL to support the general case as well.

The following function traverses quotation and replaces access to global lets with their value:

open Microsoft.FSharp.Quotations

let rec expand e = 
  match e with
  // Extract value of global 'let' bound symbols
  | Patterns.PropertyGet(None, pi, []) -> 
      Expr.Value(pi.GetValue(null, [| |]), e.Type)
  // standard recursive processing of quotations
  | ExprShape.ShapeCombination(a, b) -> 
      ExprShape.RebuildShapeCombination(a, b |> List.map expand)
  | ExprShape.ShapeLambda(v, b) -> Expr.Lambda(v, expand b)
  | ExprShape.ShapeVar(v) -> Expr.Var(v)

Then you can write the following to get a quotation that contains value instead of PropGet:

let z = 5
let eOrig = <@ Seq.filter (fun p -> p = z) [ 1 .. 10 ]@> 
let eNice = expand eOrig
Tomas Petricek