Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
137 views
in Technique[技术] by (71.8m points)

pharo - 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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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]

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...