Why shouldn't I store in literal arrays in Smalltalk?

Some leadership styles and idioms suggest that you should not mutate arrays of literals, as 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 shouldnโ€™t I do this?

+6
source share
2 answers

Note. The following is implementation dependent. ANSI Smalltalk Standard defines:

It is not known whether the values โ€‹โ€‹of the same literals are the same or different objects. It is also not indicated whether the values โ€‹โ€‹of the individual ratings of a particular literal are the same or different objects.

That is, you cannot rely on two (equal) literals that are the same or different from others. However, the following general implementation

Literary arrays in Squeak and Pharo

At least in Squeak and Pharo, literal arrays are built when the method is saved (= compiled) and stored inside the method object (a CompiledMethod ). This means that changing the literal array changes the value stored in the method object. For instance:

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

This method returns 1 only ever on the first call:

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

It does not even depend on the recipient.

But then again, you cannot rely on this ; it may be different in other Smalltalks.

Different approaches

  • Copy (always safe)
    To overcome this, you can simply copy an array of literals before using. 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 in the method object.

  • Bracketed arrays (mostly portable)
    Although not defined in the standard, most implementations support expressed array expressions, such as:

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

    which is equivalent to:

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

    These arrays are created at runtime (as opposed to literal arrays) and are therefore 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

Sometimes there are reasons to actually mutate literal arrays (or, generally speaking, any method literal, to be frank). For example, if you have static information, such as images or binary data, that do not change at all, but are not always used, but you cannot (for any reason) use instance or class variables, you can save the object in an array of literals when first use:

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

Checking ifNil: will be true only at the first start of the method, subsequent executions will simply return the value that was returned by self generateBinaryData during the first call.

This scheme has been used by some structures for some time. However, in particular, for binary data, most Smalltalks (including Squeak and Pharo) now support a literal-byte array of the form #[ โ€ฆ ] . Then this method can 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] 
+8
source

In the past, it was a source of some confusion when some method returned a literal array (or string) to someone who accidentally modified it. It is difficult to find, because the source code does not reflect the contents of the literal array.

Therefore, some Smalltalks (VisualWorks, Smalltalk / X, and possibly others) make literals immutable and will throw an exception when a literal is written (Smalltalk / X allows you to disable it at compile time if you really need this function for backward compatibility).

We have been working in our company for many years with both schemes, and we really do not miss or need variable arrays. I would argue that in a not so future version of Squeak it will be so (if not in the queue or in some kind of change file).

+2
source

Source: https://habr.com/ru/post/986286/


All Articles