为什么我不应该在 Smalltalk 中存储到文字数组中?



一些样式指南和习惯用法建议您不应该更改文字数组,比如本例中的

MyClass>>incrementedNumbers
| numbers |
numbers := #( 1 2 3 4 5 6 7 8 ).
1 to: numbers size: do: [:index |
numbers at: index put: (numbers at: index) + 1].
^ numbers

为什么我不应该那样做?

注意:以下内容取决于实现。ANSI Smalltalk标准定义:

未指定相同文字的值是相同对象还是不同对象。还未指定特定文字的单独求值的值是相同对象还是不同对象。

也就是说,你不能依赖两个(相等的)文字相同或不同。然而,以下是的常见实现

Squeak和Pharo中的文字数组

至少在Squeak和Pharo中,文本数组是在保存(=编译)方法时构造的,并存储在方法对象(CompiledMethod)内。这意味着更改文字数组会更改存储在方法对象中的值。例如:

MyClass>>example1
| literalArray |
literalArray := #( true ).
literalArray first ifTrue: [
literalArray at: 1 put: false.
^ 1].
^ 2

此方法仅在第一次调用时返回1

| o p |
o := MyClass new.
o example1. "==> 1"
o example1. "==> 2"
o example1. "==> 2"
p := MyClass new.
p example1. "==> 2"

这甚至与接收器无关。

但是,你不能依赖它,在其他Smalltalk中可能会有所不同。

不同的方法

  1. 复制(始终安全)
    要克服这一问题,您可以在使用前简单地复制文本数组。你的例子:

    MyClass>>incrementedNumbers
    | numbers |
    numbers := #( 1 2 3 4 5 6 7 8 ) copy. "<====== "
    1 to: numbers size: do: [:index |
    numbers at: index put: (numbers at: index) + 1].
    ^ numbers
    

    这始终是安全的,并且不会使方法对象中的数组发生突变。

  2. 支撑数组(大多数是可移植的)
    虽然标准中没有定义,但大多数实现都支持这样的支撑数组表达式:

    { 1 . 'foo' . 2 + 3 }. 
    

    相当于:

    Array with: 1 with: 'foo' with: 2 + 3.
    

    这些数组是在执行时构造的(与文字数组相反),因此使用起来是安全的。再次举你的例子:

    MyClass>>incrementedNumbers
    | numbers |
    numbers := { 1 . 2 . 3 . 4 . 5 . 6 . 7 . 8 }. "<====== "
    1 to: numbers size: do: [:index |
    numbers at: index put: (numbers at: index) + 1].
    ^ numbers
    

(Ab)使用文字数组

有时确实有理由对文字数组进行突变(或者更普遍地说,是任何文字方法)。例如,如果你有静态信息,比如图像或二进制数据,它们根本不会改变,但并不总是被使用,但你不能(出于任何原因)使用实例或类变量,你可能会在第一次使用时将对象存储在文字数组中:

MyClass>>staticInformation
| holder |
holder := #( nil ).
holder first ifNil: [ holder at: 1 put: self generateBinaryData ].
^ holder first

ifNil:检查仅在第一次执行该方法时为true,后续执行将仅返回self generateBinaryData在第一次调用期间返回的值。

这种模式被一些框架使用了一段时间。然而,特别是对于二进制数据,大多数Smalltalk(包括Squeak和Pharo)现在支持#[ … ]形式的文本字节数组。该方法可以简单地写成

MyClass>>staticInformation
^ #[42 22 4 33 4 33 11 4 33 0 0 0 0 
4 33 18 4 33 4 33 9 0 14 4 33 4 
33 7 4 33 0 0 9 0 7 0 0 4 33 10
4 33 4 33 7 4 33 0 0 9 0 7 0 0 4 
" ... "
33 10 4 33 4 33 17 0 11 0 0 4 33
4 33 0 0 17 0 7 0 0 4 33 13 0]

过去,当某个方法将一个文本数组(或字符串)交给意外修改它的人时,这一直是一些混乱的根源。很难找到,因为源代码没有反映文字数组的内容。

因此,一些Smalltalk(VisualWorks、Smalltalk/X,也许还有其他)使文字不可变,并在写入文字时引发异常(Smalltalk/X允许在编译时关闭此功能,以防您真的需要该功能来实现向后兼容性)。

多年来,我们一直在公司使用这两种方案,我们确实不会错过或需要可变数组。我敢打赌,在Squeak的未来版本中,情况也会如此(如果还没有在队列或某个变更文件中)。

最新更新