Smalltalk 如何防止OrderedCollection在#do:循环中删除其元素?

Smalltalk 如何防止OrderedCollection在#do:循环中删除其元素?,smalltalk,Smalltalk,考虑以下书面代码: oc do: [:elem | self doSomethingWith: elem] 众所周知,这里的潜在问题是以某种方式让#doSomethingWith:接触oc(一个OrderedCollection)并删除其中的一些元素 建议的解决方案是将上述内容写为 oc copy do: [:elem | self doSomethingWith: elem]. 好的,是的,但我们并不是每次枚举时都复制所有集合。是吗 实际的问题是,每个元素的处理过程都很难遵循,最终可能会在

考虑以下书面代码:

oc do: [:elem | self doSomethingWith: elem]
众所周知,这里的潜在问题是以某种方式让
#doSomethingWith:
接触
oc
(一个
OrderedCollection
)并删除其中的一些元素

建议的解决方案是将上述内容写为

oc copy do: [:elem | self doSomethingWith: elem].
好的,是的,但我们并不是每次枚举时都复制所有集合。是吗

实际的问题是,每个元素的处理过程都很难遵循,最终可能会在我们不知道的情况下删除元素。在我们上面的例子中,如果
oc
的某些元素在
的上下文中以某种方式被删除,那么我们将得到一个
错误。不是吗

不是真的。这个问题将被忽视。看看这个例子:

oc := #(1 2 4) asOrderedCollection.
oc do: [:i | i even ifTrue: [oc remove: i]]
在这种情况下,我们将不会获取和出错,元素
4
也不会得到处理(即,在这种情况下,它不会被删除)。因此,我们将从枚举中无声地跳过元素

为什么会这样?嗯,因为
#do:
的实现方式。以Squeak为例:

do: aBlock 
  "Override the superclass for performance reasons."
  | index |
  index := firstIndex.
  [index <= lastIndex]
    whileTrue: 
      [aBlock value: (array at: index).
      index := index + 1]
do:aBlock
“出于性能原因,重写超类。”
|索引|
索引:=第一个索引。
[index1)正如aka.nice已经指出的,获取并记住最初的lastIndex不是一个好主意。这可能会使事情变得更糟,并导致更多的麻烦

2) 所提供的OrderedCollection并没有真正准备好,并且不喜欢在对其进行迭代时修改接收方

3) 更好的解决方案是先收集要删除的元素,然后在do:-处理之后在第二步中删除它们。但是,我理解,您不能这样做

可能的解决方案:

a) 创建OrderedCollection的子类,使用重定义的do:-和重定义的removeXXX-和addXXX-方法。后面的方法需要告诉迭代器(即do方法)正在发生什么。 (如果要删除/添加的索引在当前do索引之前,请小心…)。 通知可以通过可处理的信号/异常来实现,该信号/异常在修改方法中发出,并在do循环代码中捕获

b) 创建一个包装类作为Seq.Collection的子类,该类将原始集合作为instvar,并将所选消息转发到其(包装的)原始集合。 与上面类似,在这个包装器中重新定义do:和remove/add方法,并执行相应的操作(.e.再次发出更改的信号)

如果代码需要可重入(即,如果另一个代码在包装的集合上执行循环),请注意在何处保留状态;然后必须将状态保留在do方法中,并使用信号来传达更改

然后用类似的东西列举这些收藏:

(SaveLoopWrapper on:myCollection) do:[: ...
].
并确保执行删除操作的代码也能看到包装器实例,而不是myCollection,这样才能真正捕获添加/删除操作

如果你不能理解后面的内容,我想到了另一个技巧:使用MethodWrappers,你可以改变单个实例的行为并引入钩子。 例如,创建OrderedCollection的子类,使用这些钩子,您可以:

 myColl changeClassTo: TheSubclassWithHooks
在迭代之前。
然后(由确保保护:)撤销循环后的包装。

我要说的是,不要切断你所在的分支…在迭代集合时修改集合,不管语言/库有什么问题。另一个观点是关于编程风格。我们可以改变状态,但这不一定是一个好主意。也许在这里我们只需要从更高的角度考虑她的级别和使用select:?某种功能性风格…还是select的开放编码优化:?在这种情况下,另一个风格问题。过早的优化会增加复杂性…并且在缓存中使用lastIndex不会很健壮。如果我们在集合中插入某个对象,我们不会在最后一个元素上迭代两者都不是。lastIndex将指向数组的未定义部分。如果我们从集合中删除,它不一定会触发错误,但会导致某种未定义的行为(是的,我们也有UB…).Thank@aka.nice谢谢你的评论。当程序员无法实际预测
do block
是否会最终修改集合时,我试图解决的问题就会发生。当修改是可跟踪的时,这个问题就不存在了,就像我的示例中的情况一样(甚至在不那么琐碎但易于处理的情况下)由于消息流的本质,尤其是当流结束了某些元素的处理时,尤其是当流结束时,无法解决这个问题。如果突变是不可避免的,那么考虑某种链表结构,在当前链路上运行之前缓存DO循环中的下一个元素。(或者在对头部进行操作之前缓存尾部,或者用lisp的说法缓存car/cdr).谢谢bla。很抱歉,我不能把我的问题说得更清楚。当我知道do块可能会修改集合时,我不是在寻找解决方案。我想说的是,有时程序员因为do块的复杂性而不知道这种可能性,因此错误很可能会被忽略检测可能更容易实现(至少在我使用的方言中),并且可能是开发/测试期间的一个选项。如果枚举集合使用底层容器,则#do:可以在枚举期间使此容器不可变。如果它使用索引槽,则可以在迭代期间使其自身不可变。Smalltalk/X和VisualWorks允许这一点。(据我所知,Squeak/Pharo不是;但添加不变性应该相对容易,例如使用methodWrappers和实例特定的行为修改)。谢谢bla。这就是我一直在寻找的见解。@LeandroCaniglia好的,那么你想要一个宽容的I吗