If you know that your quotation is of the form fun x ...
then it's easy:
let subst (v:'a) (Patterns.Lambda(x,b) : Expr<'a->'b>) =
b.Substitute(fun x' -> if x = x' then Some (Expr.Value v) else None)
|> Expr.Cast<'b>
subst 1 <@ fun x y -> x + y @>
If you additionally want to simplify expressions, then there are some slightly tricky questions you'll need to answer:
- Do you care about side effects? If I start with
<@ fun x y -> printfn "%i" x @>
and I substitute in 1
for x
, then what's the simplified version of <@ fun y -> printfn "%i" 1 @>
? This should print out 1
every time it's invoked, but unless you know ahead of time which expressions might cause side effects then you can almost never simplify anything. If you ignore this (assuming no expression causes side effects) then things become much simpler at the cost of fidelity.
- What does simplification really mean? Let's say I get
<@ fun y -> y + 1 @>
after substitution. Then, is it good or bad to simplify this to the equivalent of let f y = y+1 in <@ f @>
? This is definitely "simpler" in that it's a trivial expression containing just a value, but the value is now an opaque function. What if I have <@ fun y -> 1 + (fun z -> z) y @>
? Is it okay to simplify the inner function to a value, or bad?
If we can ignore side effects and we don't ever want to replace a function with a value, then you could define a simplification function like this:
let reduce (e:Expr<'a>) : Expr<'a> =
let rec helper : Expr -> Expr = function
| e when e.GetFreeVars() |> Seq.isEmpty && not (Reflection.FSharpType.IsFunction e.Type) -> // no free variables, and won't produce a function value
Expr.Value(Linq.RuntimeHelpers.LeafExpressionConverter.EvaluateQuotation e, e.Type)
| ExprShape.ShapeLambda(v, e) -> Expr.Lambda(v, helper e) // simplify body
| ExprShape.ShapeCombination(o, es) -> // simplify each subexpression
ExprShape.RebuildShapeCombination(o, es |> List.map helper)
| ExprShape.ShapeVar v -> Expr.Var v
helper e |> Expr.Cast
Note that this still might not simplify thing as much as you'd like; for example <@ (fun x (y:int) -> x) 1 @>
will not be simplified, although <@ (fun x -> x) 1 @>
will be.