Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 575
Tobias Avatar asked Apr 30 '15 09:04

Tobias


2 Answers

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]
like image 191
2 revs Avatar answered Nov 18 '22 11:11

2 revs


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

like image 24
blabla999 Avatar answered Nov 18 '22 12:11

blabla999