6

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?

2 Answers 2

8

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

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

At least in Squeak and Pharo, literal arrays are constructed when saving (= compiling) the method and are stored inside the method object (a 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:

| 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.

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

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.

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 #[ … ]. 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]
2
  • 1
    Note that instead of mutating the array you can also do more intention revealing ^ numbers collect: [ :each | each + 1 ] Commented Apr 30, 2015 at 12:17
  • 2
    Correct and better. I wrote it this way just so I can show the effect :)
    – Tobias
    Commented Apr 30, 2015 at 12:28
2

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).

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).

2
  • It's a good idea. However, Storing into literals is currently a way of leaving the system working while changing class shapes, for example.
    – Tobias
    Commented Oct 30, 2015 at 8:42
  • Sure, but both ST/X and VW have a makeMutable / beMutable, which you can use before such things (and make it mutable afterwards). Which has the added benefit of documenting your intent, and you can search for senders of it ;-).
    – blabla999
    Commented Mar 10, 2016 at 21:39

Not the answer you're looking for? Browse other questions tagged or ask your own question.