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>
假设我有 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>