Skip to content

Instantly share code, notes, and snippets.

@et1975
Last active January 10, 2017 14:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save et1975/7db86e1310667423007f to your computer and use it in GitHub Desktop.
Save et1975/7db86e1310667423007f to your computer and use it in GitHub Desktop.
F# record selection translated into C# properties init expression tree
open System.Reflection
open System
open System.Linq
open System.Linq.Expressions
let memoize f =
let dict = System.Collections.Concurrent.ConcurrentDictionary<_,_>()
fun x -> dict.GetOrAdd(x, lazy (f x)).Force()
let getProperties = memoize(fun (t:Type) -> t.GetProperties(BindingFlags.Instance ||| BindingFlags.Public ||| BindingFlags.SetProperty).ToDictionary(fun p -> p.Name.ToLower()))
/// translate f# record init syntax tree into class property init tree:
/// r -> v1 -> v2 ... invoke(v2).invoke(v1)
/// into:
/// new { prop1 = v1, prop2 = v2 }
let translate x:Expression<Func<'T,'T>> =
let rec extract (args:Expression seq) (ps:ParameterExpression seq) sx =
match box sx with
| :? LambdaExpression as lambda ->
match lambda.Body with
| :? NewExpression as n ->
let props = getProperties typeof<'T>
let vals = lambda.Parameters |> Seq.append ps |> Seq.skip 1 // skip $
let bindings =
vals.Select(fun p -> props.TryGetValue(p.Name.ToLower())).Where(fun (f,_) -> f)
|> Seq.zip args
|> Seq.map (fun (a, (_,p)) -> Expression.Bind(p, a))
|> Seq.append
(n.Constructor.GetParameters()
.Select(fun p i -> (p, n.Arguments.[i]))
.Where(fun (_, a) -> a.NodeType = ExpressionType.Constant)
.Select(fun (p, a) -> Expression.Bind(props.[p.Name.ToLower()], a)))
|> Seq.map (fun b -> b :> MemberBinding)
|> Array.ofSeq
Expression.Lambda<Func<'T,'T>>(Expression.MemberInit(Expression.New(typeof<'T>),bindings), lambda.Parameters |> Seq.append ps |> Seq.take 1) // take $
| :? MethodCallExpression as call ->
extract (Seq.append args call.Arguments) (Seq.append ps lambda.Parameters) call.Object
| _ -> failwith "Don't know how to translate the expression"
| _ -> x
extract Seq.empty Seq.empty x
@et1975
Copy link
Author

et1975 commented Jan 10, 2017

Usage:

/// compiler coercion to produce Linq expressions from F# lambdas
type Quote<'T> = 
    static member X(exp:Expression<Func<'T,'a>>) = exp

then using a table from Cassandra.Data.Linq:

let update where what =
    table.Where(where).Select(translate what).Update().Execute()

Given a type, mapped to the table:

type SomeRecord = { id : int; someField : string }

we can call the update, something like this:

update (Quote.X (fun x -> x.id = 1)) (Quote.X (fun x -> {x with someField = "one"}))

resulting in efficient partial update Cassandra instruction.

Note that conversion of F# quotations to expression trees is automatic in some cases and with a bit of creative design update calls can be made a lot less verbose.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment