F#引用的部分应用程序

6
假设我有这样一种类型的报价 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 @>

虽然@kvb的答案提供了简化表达式的有用提示,但我建议提出另一个具体问题。SO真正擅长于小型、自包含的问题和答案。 - CaringDev
明白了 @CaringDev - tejas
2个回答

4

如果你知道你的引用是形如 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 @>是好还是坏?这肯定更“简单”,因为它只包含一个值的平凡表达式,但该值现在是不透明的函数。如果我有<@ 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 @> 将被简化。

这不会计算可以计算的部分,对吧?例如,当调用subst true x y expr时,应该得到fun x y -> x + y。 注:此处“expr”未提供上下文信息,因此翻译为“表达式”。 - tejas
谢谢@kvb,你给了我足够的线索。 - tejas

2

拼接是一种方便的嵌套引用的方式:

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>

拼接是一个值得了解的好方法,但我认为它并不能回答这个问题(即使在编辑之前):“reduce 1 q”的结果相当于“<@ (fun x y -> x + y) 1 @>”,而不是所期望的“<@ fun y -> 1 + y @>”。虽然它们在语义上是等价的,但在处理引用时,人们通常也关心语法结构。 - kvb
@kvb 当然可以!这应该只是提供了一个更广泛的视角,而且在我看来适用于“部分应用...”和“本质上...”。其他人偶然发现这个问题时可能仍会发现我的答案有用,所以我暂时把它留在这里。 - CaringDev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接