Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending Query Expressions

Tags:

f#

Are there any documents or examples out there on how one can extend/add new keywords to query expressions? Is this even possible?

For example, I'd like to add a lead/lag operator.

like image 829
Dave Avatar asked Dec 11 '12 21:12

Dave


2 Answers

In addition to the query builder for the Rx Framework mentioned by @pad, there is also a talk by Wonseok Chae from the F# team about Computation Expressions that includes query expressions. I'm not sure if the meeting was recorded, but there are very detailed slides with a cool example on query syntax for generating .NET IL code.

The source code of the standard F# query builder is probably the best resource for finding out what types of operations are supported and how to annotate them with attributes.

The key attributes that you'll probably need are demonstrated by the where clause:

[<CustomOperation("where",MaintainsVariableSpace=true,AllowIntoPattern=true)>]
member Where : 
  : source:QuerySource<'T,'Q> * 
    [<ProjectionParameter>] predicate:('T -> bool) -> QuerySource<'T,'Q>

The CustomOperation attribute defines the name of the operation. The (quite important) parameter MaintainsVariableSpace allows you to say that the operation returns the same type of values as it takes as the input. In that case, the variables defined earlier are still available after the operation. For example:

query { for p in db.Products do
        let name = p.ProductName
        where (p.UnitPrice.Value > 100.0M)
        select name }

Here, the variables p and name are still accessible after where because where only filters the input, but it does not transform the values in the list.

Finally, the ProjectionParameter allows you to say that p.UnitValue > 100.0M should actually be turned into a function that takes the context (available variables) and evaluates this expression. If you do not specify this attribute, then the operation just gets the value of the argument as in:

query { for p in .. do
        take 10 }

Here, the argument 10 is just a simple expression that cannot use values in p.

like image 114
Tomas Petricek Avatar answered Oct 22 '22 15:10

Tomas Petricek


Pretty cool feature for the language. Just implemented the reverse to query QuerySource.

Simple example, but just a demonstration.

module QueryExtensions

type ExtendedQueryBuilder() =
    inherit Linq.QueryBuilder()
    /// Defines an operation 'reverse' that reverses the sequence    
    [<CustomOperation("reverse", MaintainsVariableSpace = true)>]
    member __.Reverse (source : Linq.QuerySource<'T,System.Collections.IEnumerable>) =
        let reversed = source.Source |> List.ofSeq |> List.rev
        new Linq.QuerySource<'T,System.Collections.IEnumerable>(reversed)


let query = ExtendedQueryBuilder()

And now it being used.

let a = [1 .. 100]

let specialReverse = 
    query {
        for i in a do
        select i
        reverse
    }
like image 30
Dave Avatar answered Oct 22 '22 17:10

Dave