Skip to content

Instantly share code, notes, and snippets.

@DejanMilicic
Forked from MichaelGG/ExpressionHelper.fs
Created August 5, 2022 09:25
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 DejanMilicic/9b574cdfa09944572ae5723a3c628231 to your computer and use it in GitHub Desktop.
Save DejanMilicic/9b574cdfa09944572ae5723a3c628231 to your computer and use it in GitHub Desktop.
Example module that modifies F# expressions to be more C#-ish Expression<T>s
#if INTERACTIVE
#r "FSharp.PowerPack"
#r "FSharp.PowerPack.Linq"
#endif
open System
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Linq.QuotationEvaluation
open System.Linq.Expressions
module ExpressionHelper =
let private getNode<'a when 'a :> Expression> (xty : ExpressionType) (expr : Expression) =
match expr with
| :? 'a as x when x.NodeType = xty -> Some x
| _ -> None
let (|TypeAs|_|) x = getNode<UnaryExpression> ExpressionType.TypeAs x
let (|Lambda|_|) x = getNode<LambdaExpression> ExpressionType.Lambda x
let (|Call|_|) x = getNode<MethodCallExpression> ExpressionType.Call x
let (|MemberAccess|_|) x = getNode<MemberExpression> ExpressionType.MemberAccess x
let UnwrapQuotation (q:Expr) =
let exp = q.ToLinqExpression()
match exp with
| Call(x) -> x.Arguments.[0]
| x -> x
let private fixTa (l : LambdaExpression) =
match l.Body with
| TypeAs(ta) ->
let newBody = Expression.Convert(ta.Operand, ta.Type)
Expression.Lambda(l.Type, newBody, l.Parameters)
| _ -> l
let rec fixQ e : Expr =
match e with
// Hack, should look up the right MethodInfo instead of relying on names
| Patterns.Call (None, mi, [x]) when mi.Name = "CreateSequence" -> fixQ x
| Patterns.Call (None, mi, [x]) when mi.Name = "Box" -> Expr.Coerce(fixQ x, typeof<obj>)
| ExprShape.ShapeCombination(o, exprs) -> ExprShape.RebuildShapeCombination(o, List.map fixQ exprs)
| ExprShape.ShapeLambda(v, e) -> Expr.Lambda(v, fixQ e)
| ExprShape.ShapeVar(v) -> e
let ConvertFunc (q : Expr<'a -> 'b>) =
let q = fixQ q
match UnwrapQuotation q with
| Lambda(l) ->
let l = fixTa l
Expression.Lambda<Func<'a, 'b>>(l.Body, l.Parameters)
| x -> failwithf "Expression type %A not supported." x.NodeType
let ConvertFuncObj (q : Expr<'a -> 'b>) =
let x = ConvertFunc q
let conv = Expression.Convert(x.Body, typeof<obj>)
Expression.Lambda<Func<'a, obj>>(conv, x.Parameters)
let private valueToLambda<'a, 'b> (q : Expr<'b>) =
let rec setvar (p:Var) e : Expr =
match e with
| Patterns.Value (null, t) when t = typeof<'a> -> Expr.Var p
| Patterns.PropertyGet(None, pi, []) when pi.PropertyType = typeof<'a> -> Expr.Var p
| Patterns.PropertyGet(Some _, pi, []) when pi.PropertyType = typeof<'a> -> Expr.Var p
| ExprShape.ShapeCombination(o, exprs) -> ExprShape.RebuildShapeCombination(o, List.map (setvar p) exprs)
| ExprShape.ShapeLambda(v, e) -> Expr.Lambda(v, setvar p e)
| ExprShape.ShapeVar(v) -> e
let v = Var("__x", typeof<'a>, false)
let q = setvar v q
let l = Expr.Lambda(v, q)
Expr.Cast<('a -> 'b)> l
let ConvertValue<'a, 'b> (q : Expr<'b>) =
printfn "Converting: %A" q
let l = valueToLambda<'a, 'b> q
printfn "ConvertedL: %A" l
let res = ConvertFunc l
printfn "ConvertedF: %A" res
res
let ConvertValueObj<'a, 'b> (q : Expr<'b>) =
let l = valueToLambda<'a, 'b> q
ConvertFuncObj l
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment