F#中闭包中的可变变量

4

我想将一些Java代码转换成F #,以生成围绕给定点的多维点网格。 我想出了这个:

let gridGenerator midpoint stepSize steps = 
    seq {
        let n = Array.length midpoint
        let direction = Array.create n -steps
        let mutable lastIndex = n-1
        while lastIndex>=0 do
            let next = midpoint |> Array.mapi (fun i x -> x+ direction.[i]*stepSize)
            while lastIndex>=0 && direction.[lastIndex]=steps do 
                direction.[lastIndex]<- (-steps)
                lastIndex<-lastIndex-1;        
            if lastIndex>=0 then
                direction.[lastIndex]<-direction.[lastIndex]+1;
                lastIndex <- n-1;
            yield next;
    }

除了代码写得非常命令式(我会感激有关如何修复它的提示),我还遇到了编译错误:

Program.fs(18,15): error FS0407: 可变变量 'lastIndex' 的使用方式是无效的。可变变量不能被闭包捕获。考虑消除对变量的修改或使用通过'ref'和'!'进行堆分配的可变引用单元。

如何解决此错误?如何使其更加函数化?
示例:对于中点[|0.0,1.0|],步长0.5和步数1,我期望得到以下任意一种输出结果。
seq{[|-0.5, 0.5|], [|-0.5, 1.0|], [|-0.5, 1.5|], [|0.0, 0.5|], [|0.0, 1.0|], [|0.0, 1.5|], [|0.5, 0.5|], [|0.5, 1.0|], [|0.5, 1.5|]}

请注意,这将被执行多次,因此性能至关重要。

可变变量不能被闭包捕获。这是 F# 的一个严重设计 WTF。 - leppie
3
错误信息告诉你该怎么做:使用ref - svick
1
@leppie - 这可以避免C#中可能发生的一整类微妙错误 - 例如请参见Eric Lippert的博客 - John Palmer
1
@leppie 如果你不需要手把手的指导,为什么不用C++编程呢?更好的选择是使用汇编语言——那里没有任何手把手的指导。 - Onorio Catenacci
1
@leppie,请查看http://lorgonblog.wordpress.com/2008/11/12/on-lambdas-capture-and-mutability/,了解设计理念。 - Brian
显示剩余7条评论
3个回答

4
let gridGenerator midpoint stepSize steps =
    seq {
        let n = Array.length midpoint
        let direction = Array.create n -steps
        let lastIndex = ref (n - 1)
        while !lastIndex >= 0 do
            let next = midpoint |> Array.mapi (fun i x -> x + direction.[i] * stepSize)
            while !lastIndex >= 0 && direction.[!lastIndex] = steps do
                direction.[!lastIndex] <- -steps
                decr lastIndex
            if !lastIndex >= 0 then
                direction.[!lastIndex] <- direction.[!lastIndex] + 1
                lastIndex := n - 1
            yield next
    }
< p >"ref"非常适合此类用途,并且不被视为可变变量(因为它们不是可变的)。


那么ref仅仅是一个盒子(在函数术语中)吗? - leppie
编译通过了。但是为什么到处都是 int 类型?我以为 F# 应该能够泛化类型,而不是猜测 int? - Grzenio
0和1属于“int”类型,另外默认的“decr”函数只能使用“int”。我还认为数组索引必须是整数(这是.NET框架设计中的一个缺陷)。 - Ramon Snir

4
这里有一种更加实用的做法:
let rec gridGenerator midpoint stepSize steps =
    match midpoint with
    | [] -> Seq.singleton []
    | p::point ->
        seq {
            for d in - stepSize * steps .. stepSize .. stepSize * steps do
                for q in gridGenerator point stepSize steps do
                    yield (p + d) :: q
        }

还有签名:

val gridGenerator : int list -> int -> int -> seq<int list>

如果您要重复使用结果,请记得缓存它或将其转换为数组或列表。


3
现在你可以使用F# 4,它没有这样的限制。

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