F# 引用的部分应用

Partial applications of F# Quotations

假设我有 Quotations.Expr<(int -> int -> int)>

类型的报价单
<@ fun x y -> x + y @>

我想创建一个函数 fun reduce x expr,当调用 reduce 1 expr 时基本上会产生

<@ fun y -> 1 + y @>

即我想部分应用一个引文来生成另一个引文。

我确定这是可行的,有人有什么想法吗?以前有人尝试过吗?似乎找不到任何东西。

此外,我对 LISP 不是很熟悉 -- 但这与我使用 LISP 宏可以实现的本质上相似吗?

更新: 在减少引用的同时,我想评估结果表达式树中可以评估的部分。

例如:reduce true <@ fun b x y -> if b then x + y else x - y@> 应导致 <@ fun x y -> x + y @>

如果您知道您的报价是 fun x ... 的形式,那么很容易:

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 @>

如果您还想简化表达式,那么您需要回答一些稍微棘手的问题:

  • 你关心副作用吗?如果我从 <@ fun x y -> printfn "%i" x @> 开始,然后用 1 代替 x,那么 <@ fun y -> printfn "%i" 1 @> 的简化版本是什么?这应该在每次调用时打印出 1,但除非您提前知道哪些表达式可能会导致副作用,否则您几乎永远无法简化任何事情。如果您忽略这一点(假设没有表达式会导致副作用),那么事情会变得更加简单,但会牺牲保真度。
  • 简化的真正含义是什么?假设我在替换后得到 <@ fun y -> y + 1 @> 。那么,将其简化为let f y = y+1 in <@ f @>的等价物是好是坏?这绝对是 "simpler",因为它是一个只包含一个值的普通表达式,但该值现在是一个不透明的函数。如果我有 <@ fun y -> 1 + (fun z -> z) y @> 怎么办?将内部函数简化为一个值是可以的,还是不好的?

如果我们可以忽略副作用并且我们不想用值替换函数,那么您可以像这样定义一个简化函数:

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

请注意,这仍然可能无法像您希望的那样简化事情;例如 <@ (fun x (y:int) -> x) 1 @> 不会被简化,尽管 <@ (fun x -> x) 1 @> 会被简化。

拼接是一种在引语中嵌入引语的便捷方式:

let reduce x expr =
    <@ (%expr) x @>

reduce 的类型为 'a -> Expr<('a -> 'b)> -> Expr<'b>

用法:

let q = <@ fun x y -> x + y @>
let r = reduce 1 q // Expr<int -> int>
let s = reduce 2 <| reduce 3 q // Expr<int>
let t = reduce "world" <@ sprintf "Hello %s" @> // Expr<string>