简体   繁体   English

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

[英]Why shouldn't I store into literal arrays in Smalltalk?

Some style-guides and idioms suggest that you should not mutate literal arrays, like in this case: 一些样式指南和习语表明你不应该改变文字数组,就像在这种情况下:

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

Why should I not do that? 我为什么不这样做?

Note: The following is implementation dependent. 注意:以下是依赖于实现的。 The ANSI Smalltalk Standard defines: ANSI Smalltalk标准定义:

It is unspecified whether the values of identical literals are the same or distinct objects. 未指定相同文字的值是相同还是不同的对象。 It is also unspecified whether the values of separate evaluations of a particular literal are the same or distinct objects. 还未指定特定文字的单独评估的值是相同还是不同的对象。

That is you cannot rely on two (equal) literals being the same or being different whatsoever. 也就是说,你不能依赖两个(相等的)文字是相同的或不同的。 However, the following is a common implementation 但是,以下是一种常见的实现方式

Literal Arrays in Squeak and Pharo Squeak和Pharo中的文字阵列

At least in Squeak and Pharo, literal arrays are constructed when saving (= compiling) the method and are stored inside the method object (a CompiledMethod ). 至少在Squeak和Pharo中,在保存(=编译)方法时构造文字数组,并将其存储方法对象( CompiledMethod )中。 This means that changing a literal array changes the value stored in the method object . 这意味着更改文字数组会更改存储在方法对象中的值。 For example: 例如:

MyClass>>example1

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

This method returns 1 only ever on the first invocation: 此方法仅在第一次调用时返回1

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

This is even independent of the receiver. 这甚至独立于接收器。

But again, you can't rely on that , it might be different in other Smalltalks. 但同样, 你不能依赖它 ,它可能与其他Smalltalks不同。

Different approaches 不同的方法

  1. Copying (always safe) 复制(始终安全)
    To overcome that, you can simply copy the literal array before use. 为了克服这个问题,您可以在使用前简单地复制文字数组。 Your example: 你的例子:

     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 

    This is always safe and will not mutate the array within the method object. 这总是安全的,不会改变方法对象中的数组。

  2. Braced arrays (mostly portable) 支撑阵列(主要是便携式)
    While not defined in the standard, most implementations support braced array expressions like this: 虽然未在标准中定义,但大多数实现都支持这样的支撑数组表达式:

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

    which is equivalent to: 这相当于:

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

    These arrays are constructed at execution time (in contrast to literal arrays) and are hence safe to use. 这些数组是在执行时构造的(与文字数组相反),因此可以安全使用。 Your example again: 再次举例:

     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)using literal arrays (Ab)使用文字数组

There are sometimes reasons to actually mutate literal arrays (or more generally any method literal, to be frank). 实际上有时会有理由改变文字数组(或者更常见的是任何方法文字,坦率地说)。 For example, if you have static information, like images or binary data that don't change at all but are not always used, but you cannot (for whatever reason) use instance or class variables, you might store the object in a literal array upon first use: 例如,如果您有静态信息,如图像或二进制数据,它们根本不会更改但不会一直使用,但您不能(无论出于何种原因)使用实例或类变量,您可以将对象存储在文字数组中首次使用时:

MyClass>>staticInformation

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

The ifNil: check will only be true the first time the method is executed, subsequent executions will just return the value that was returned by self generateBinaryData during the first invocation. ifNil: check仅在第一次执行方法时才为true,后续执行将仅返回self generateBinaryData在第一次调用时返回的值。

This pattern was used by some frameworks for a while. 一些框架暂时使用了这种模式。 However, specifically for binary data, most Smalltalks (including Squeak and Pharo) now support a literal byte array of the form #[ … ] . 但是,特别是对于二进制数据,大多数Smalltalks(包括Squeak和Pharo)现在都支持#[ … ]形式的文字字节数组 The method can then simply be written as 然后可以简单地将该方法写成

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]

It has been a source of quite some confusion in the past, when some method handed out a literal array (or string) to someone who modified it by accident. 过去,当某些方法将字面数组(或字符串)分发给偶然修改它的人时,它一直是一个混乱的源头。 Hard to find, because the source code does not reflect the contents of the literal array. 很难找到,因为源代码没有反映文字数组的内容。

Therefore, some Smalltalks (VisualWorks, Smalltalk/X and maybe others) make literals immutable and will raise an exception, when the literal is written to (Smalltalk/X allows for this to be switched off at compilation time, in case you really really need that feature for backward compatibility). 因此,一些Smalltalks(VisualWorks,Smalltalk / X和其他人)使文字不可变并且在写入文字时会引发异常(Smalltalk / X允许在编译时关闭它,以防你确实需要该功能是为了向后兼容)。

We have been working at our company for years with both schemes, and we really do not miss or need mutable arrays. 我们一直在我们公司工作多年,我们真的不会错过或需要可变阵列。 I would bet that in a not so future version of Squeak, this will be also the case (if not already in the queue or in some changefile). 我敢打赌,在不久的Squeak版本中,情况也是如此(如果还没有在队列中或某些更改文件中)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM