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

Las*_*olt 5 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 @>
Run Code Online (Sandbox Code Playgroud)

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>.
Run Code Online (Sandbox Code Playgroud)

正如你所看到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
Run Code Online (Sandbox Code Playgroud)

报价.你的答案给出(h 3 <@ 1 @>).Eval() = 4了上述收益的位置h 3 1 = 7.在这里,我想7成为答案.

Tom*_*cek 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)))
Run Code Online (Sandbox Code Playgroud)

但是,这是一个非常人为的例子(用于演示MetaOCaml中的变量捕获,这在F#中不可用).它只是生成表达式(2 + (1 + ...)).您可以通过编写以下内容来获得相同的结果:

let rec h n (z:Expr<int>) = 
  if n = 0 then z                
  else h (n - 1) <@ n + %z @>
Run Code Online (Sandbox Code Playgroud)

甚至更好:

[ 1 .. 4 ] |> List.fold (fun st n -> <@ n + %st @>) <@ 0 @>
Run Code Online (Sandbox Code Playgroud)

我也在F#引用中遇到了这个限制,如果支持它会很好.但是,我不认为它在实践中是一个大问题,因为F#引用不用于分阶段元编程.它们对于分析现有F#代码比生成代码更有用.

  • @lasseespeholt - MetaOCaml肯定更适合这个目的.顺便说一句:我很想知道你的场景是什么(只是为了更好地理解多阶段编程的实际用途). (2认同)