集合上的高阶函数是否保证按顺序执行?

时间:2011-11-11 11:37:20

标签: collections groovy higher-order-functions

在另一个问题中,用户建议编写类似的代码:

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 )的执行顺序?以上代码是否已损坏,或者可以吗?

2 个答案:

答案 0 :(得分:3)

由于上面提到的原因,我不喜欢在我的闭包中依赖明确的外部变量。

事实上,我必须定义的变量越少,我就越快乐; - )

对于可能并行的东西,如果单个线程要处理得太多,那么代码总是用一些GPars loveliness级别来包装它。为此,正如您所说,您希望尽可能少的可变性并尝试完全避免副作用(例如上面的外部计数器模式)

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

InvokerHelper.asIterator(再次Map)基本上调用传入的对象上的iterator()方法。

因此,对于source is here等,它将按迭代器定义的顺序迭代对象。

因此可以编写自己的类,该类遵循Lists设计(不需要实现Iterable,感谢duck-typing),并定义如何迭代集合

我想通过询问Groovy规范,这个答案可能不是你想要的,但我不认为有答案。 Groovy从来没有真正拥有“完整”的规范(事实上,这是关于Iterable interface的常规问题。

答案 1 :(得分:1)

我认为保持通过collectfindAll副作用的函数是一般的好主意,不仅是为了保持低复杂度,而且在并行执行时使代码更加并行友好将来需要。

但是在each的情况下,保持函数副作用没有多大意义,因为它不会做任何事情(事实上,这种方法的唯一目的是取代act作为for - 每个循环)。 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函数提供collect和类似的方法(参见Jira issue),这个例子就没有意义了,但它有点表明有时候实用性比纯度高IMO:)