我有这段代码:
let rec h n z = if n = 0 then z
else <@ (fun x -> %(h (n - 1) <@ x + %z @>)) n @>
从http://www.cs.rice.edu/~taha/publications/journal/dspg04a.pdf
中的MetaOcaml示例转换而来在论文中,解释了上面的例子将产生以下参数3
和.<1>.
(在MetaOcaml表示法中):
.<(fun x_1 -> (fun x_2 -> (fun x_3 -> x_3 + (x_2 + (x_1 + 1))) 1) 2) 3>.
正如您所看到的,x
被x_1
,x_2
等取代,因为x
只会引用x
最里面的fun
。
但是在F#中这是不允许的。我得到编译时错误:“变量'x'在引号中绑定,但用作拼接表达式的一部分。这是不允许的,因为它可能会逃避其范围。”所以问题是:如何改变它以便编译并具有与MetaOcaml输出相同的语义?
更新评论:我使用PowerPack来实际评估报价。但我不认为这与它有任何关系,因为错误是在编译时。到目前为止,QuotationEvaluation仍然有效。但是,我知道它可能不是最有效的实现。
更新Tomas的回答:
我真的不希望x
是全局的,或者是为了逃避范围。但我想要的是相当于
let rec h n z = if n = 0 then z
else (fun x -> (h (n - 1) (x + z))) n
带引号。您的答案为(h 3 <@ 1 @>).Eval() = 4
,其中上述结果为h 3 1 = 7
。在这里,我希望7
成为答案。
答案 0 :(得分:6)
F#语法不支持可能会超出范围的变量,因此您需要使用Expr
操作显式构造树。这样的事情可以解决问题:
open Microsoft.FSharp.Quotations
let rec h n (z:Expr<int>) =
if n = 0 then z
else
let v = new Var("x", typeof<int>)
let ve = Expr.Var(v)
Expr.Cast<int>
(Expr.Application( Expr.Lambda(v, h (n - 1) <@ %%ve + %z @>),
Expr.Value(n)))
然而,这是一个非常人为的例子(用于演示MetaOCaml中的变量捕获,这在F#中不可用)。它只生成(2 + (1 + ...))
之类的表达式。你可以通过写下这样的结果得到相同的结果:
let rec h n (z:Expr<int>) =
if n = 0 then z
else h (n - 1) <@ n + %z @>
甚至更好:
[ 1 .. 4 ] |> List.fold (fun st n -> <@ n + %st @>) <@ 0 @>
我也在F#引用中遇到了这个限制,如果支持它会很好。但是,我不认为它在实践中是一个大问题,因为F#引用不用于分阶段元编程。它们对于分析现有的F#代码比生成代码更有用。