函数式编程:地图是连续的吗?对闭包的影响

4
我将用Julia进行说明:
假设我有一个名为counter()的闭包函数。
function mycl()                                                                                                                                                                                                    
        state=0                                                                                                                                                                                                    
        function counter()                                                                                                                                                                                         
                state=state+1                                                                                                                                                                                      
        end                                                                                                                                                                                                        
end  

现在假设我创建了函数mycounter:

mycounter=mycl()

现在将此函数映射到一个长度为10,所有元素均为1的数组上。

map(x->x+mycounter(),ones(1:10))

输出结果如下:
julia> map(x->x+mycounter(),ones(1:10))
10-element Array{Int64,1}:
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11

看起来这个函数是按顺序应用于要映射的数组。

最终我想避免使用for循环,但是由于闭包的局部状态变量会改变状态,我需要按顺序应用它。这似乎是这种情况,这是一种采用的标准吗?(还没有使用*apply测试等效的R版本)。而且,由于局部状态变量正在变异,这真的是“功能性”的吗?


这在 map 函数中是成立的。如果你尝试使用 pmap 代替,你会发现输出将会类似于 [2 2 2 2 2 3 3 3 4 4] 等,取决于你定义的进程数量。而且,是的,鉴于你的函数具有状态,它不是一个函数式函数,在这种情况下输出是完全不可预测的。 - Tasos Papastylianou
3
在需要按顺序执行的情况下,应该使用reducefold(在Julia中叫什么都可以),而不是mapmap的目的是创建一个新的容器,而不是执行副作用。 - Bergi
3个回答

4
当前的Julia实现中,map会按顺序将其函数参数应用于集合参数,尽管这不是一个明确记录的特性。在未来,当多线程成为非实验性语言特性时,评估顺序可能会改变,但这不会没有警告地发生。也有可能不通过改变map的行为来实现,而是作为一种选择性功能,例如通过tmap(“线程映射”)的方式,或者在编译器知道被映射的函数是纯函数的情况下进行优化。

有一个名为pmap的函数可以实现这一点,因此我认为假设map始终是顺序的是合理的。此外,如果没有针对纯函数的优化,我会非常惊讶,因为检查它非常容易。 - Tasos Papastylianou
pmap函数用于分布式并行映射,而不是当前进程中的线程映射。当然,有人可能需要两者,但这是未来API设计问题。没有优化来在多个线程上运行纯函数,因为在除主线程之外的线程中运行Julia代码仍然是一项实验性功能(尽管越来越稳定)。 - StefanKarpinski

1
首先回答第二个问题,不,你的代码不是纯函数式的,因为它会改变状态。
关于第一个问题,这取决于每种语言。在Scheme中,R7RS说:“map过程将proc逐个应用于list的元素并以顺序返回结果列表”(第51页,我强调了),其中“顺序”似乎有些含糊不清,是指列表的顺序还是列表中元素的顺序(也许是前者)。在OCaml中,manualList.map“将函数f应用于a1,...,an,并使用f返回的结果构建列表[f a1; ...; f an]”,这也是模棱两可的,但其implementation明确地使用let按顺序写成了a::l -> let r = f a in r :: map f l

1
R7RS 在引用后面指出:“对于将 proc 应用于 list 元素的动态顺序是未指定的。” - Luis Casillas

1
你要找的纯函数是“累积映射”,它避免了状态,而是将累积参数线程化。例如,可以参考各种haskell实现。
在Julia中实现这个函数也相当简单,虽然可以认为for循环更合适。无论如何,使用单个可变状态变量的for循环比使用闭包突变状态的高阶函数要好得多,因为前者中的突变是明显且包含在内,而后者中的突变在调用map函数时不清楚。

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