F#引用:变量可能会逃避范围

时间:2011-06-20 16:06:25

标签: f# metaprogramming multi-stage-programming metaocaml

我有这段代码:

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

正如您所看到的,xx_1x_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成为答案。

1 个答案:

答案 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#代码比生成代码更有用。