【问题标题】:Are higher order functions on collections guaranteed to be executed sequentially?集合上的高阶函数是否保证按顺序执行?
【发布时间】:2011-11-11 11:37:19
【问题描述】:

在另一个问题中,一位用户建议编写这样的代码:

def list = ['a', 'b', 'c', 'd']
def i = 0; 
assert list.collect { [i++] } == [0, 1, 2, 3]

这样的代码在其他语言中被认为是不好的做法,因为 collect 的内容会改变其上下文的状态(这里它会改变 i 的值)。换句话说,闭包有副作用。

这样的高阶函数应该能够并行运行闭包,并再次将其组装到一个新列表中。如果闭包中的处理是长时间的、CPU 密集型操作,则可能值得在单独的线程中执行它们。将collect 更改为使用ExecutorCompletionService 来实现这一点很容易,但它会破坏上述代码。

另一个问题示例是,如果 collect 出于某种原因以相反的顺序浏览集合,在这种情况下,结果将是 [3, 2, 1, 0]。请注意,在这种情况下,列表没有被还原,0 确实是对 'd' 应用闭包的结果!

有趣的是,这些函数在Collection's JavaDoc 中以“迭代此集合”的形式记录,这表明迭代是顺序的。

groovy 规范是否明确定义了高阶函数(如collecteach)中的执行顺序?上面的代码是坏了,还是没问题?

【问题讨论】:

    标签: collections groovy higher-order-functions


    【解决方案1】:

    由于您上面给出的原因,我不喜欢在我的闭包中依赖显式的外部变量。

    确实,我需要定义的变量越少,我就越快乐 ;-)

    对于可能并行的事情,如果单线程无法处理太多,请始终编写代码以使用某种级别的GPars loveliness 包装它。为此,正如您所说,您希望尽可能少的可变性并尝试完全避免副作用(例如上面的外部计数器模式)

    至于问题本身,如果我们以collect 为示例函数,并检查the source code,我们可以看到给定ObjectCollectionMap 以类似的方式完成Iterator 的引用方式略有不同)它沿 InvokerHelper.asIterator(self) 迭代,将每个闭包调用的结果添加到结果列表中。

    InvokerHelper.asIterator(又是source is here)基本上是在传入的Object上调用iterator()方法。

    所以对于Lists 等,它将按照迭代器定义的顺序向下迭代对象。

    因此可以编写您自己的遵循Iterable interface 设计的类(由于duck-typing,不需要实现Iterable),并定义如何迭代集合。

    我认为通过询问 Groovy 规范,这个答案可能不是您想要的,但我认为没有答案。 Groovy 从来没有真正的“完整”规范(事实上,some people dislike 是关于 groovy 的一点)。

    【讨论】:

    • 更新了我的答案以添加指向 CollectionsListsObjects 之间差异的链接,这些差异恰好有一个 iterator() 方法;-)
    • 您是否发布了指向源代码的链接以希望人们会在作者标签中注意到您的名字?没有人喜欢炫耀(不过对我有用)。
    • 谢谢,我想我不能期待更好的答案。
    • @Antoine 你可以试试 groovy-user 邮件列表
    • @Don hehe..虽然在作者名单上很靠后 ;-)
    【解决方案2】:

    我认为保持函数通过 collectfindAll 无副作用通常是一个好主意,不仅可以保持较低的复杂性,而且可以使代码在需要并行执行的情况下对并行更友好未来。

    但是在each 的情况下,保持函数副作用没有多大意义,因为它不会做任何事情(事实上,这种方法的唯一目的是替换作为 for-each环形)。 Groovy's documentation 有一些使用 each(及其变体,eachWithIndexreverseEach)的示例,需要定义执行顺序。

    现在,从务实的角度来看,我认为有时可以在 collect 之类的方法中使用具有一些副作用的函数。例如,要转换[index, value] 中的列表,将transpose 和范围can be used 配对

    def list = ['a', 'b', 'c']
    def enumerated = [0..<list.size(), list].transpose()
    assert enumerated == [[0,'a'], [1,'b'], [2,'c']]
    

    甚至是inject

    def enumerated = list.inject([]) { acc, val -> acc << [acc.size(), val] }
    

    但是collect 和计数器也可以解决问题,我认为结果是最易读的:

    def n = 0, enumerated = list.collect{ [n++, it] }
    

    现在,如果 Groovy 提供了带有 index-value-param 函数的 acollect 和类似方法(参见 Jira issue),这个例子就没有意义了,但它有点表明,有时实用性胜过纯度 IMO :)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-07-22
      • 2013-07-06
      • 2014-10-13
      • 1970-01-01
      • 2019-10-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多