1. 21
  1. 6

    My theory is that ‘become:’ is a hack that was added to Smalltalk in order to take advantage of the object table. In that context, it is a very simple and cheap way to change out the representation of an object when it changes state. If you don’t have an object table, then it’s a very bad idea to use become because of the performance cost.

    The other thing is that Smalltalk is an unsafe language, in my opinion more unsafe than even C, because of this ‘become:’ operator. A fun way to crash a Smalltalk system is to evaluate ‘true become: false’, which is far more damaging than scribbling garbage past the end of an array in C, because all boolean operations everywhere in the system stop working.

    1. 7

      The other thing is that Smalltalk is an unsafe language, in my opinion more unsafe than even C, because of this ‘become:’ operator. A fun way to crash a Smalltalk system is to evaluate ‘true become: false’, which is far more damaging than scribbling garbage past the end of an array in C, because all boolean operations everywhere in the system stop working.

      The difference is, in my mind, crucial: Memory safety bugs in C allow your program to enter states that cannot be expressed within the C abstract machine. The become: method in Smalltalk does not. It may allow you to enter states that no sane developer would ever want to do intentionally, but they are still within the Smalltalk abstract machine. If you define a C function that tests a pointer against null, use it ubiquitously, and invert its meaning then you’re doing something very similar to true become: false in Smalltalk, but now your program will do things that the C abstract machine does not let you reason about. In the Smalltalk version, you can (at the source level) reason about exactly what the consequences will be. They will be awful (really, don’t do it! This is something every Smalltalk programmer does exactly once - make sure you save your image before you do!) but they won’t be unexpected.

      1. 1

        It’s similar to the newtype hack in Haskell. Imagine an object instance as an idealized combination of state and behavior: Literally, we’ll have fat pointers which point to a closure and a class (Smalltalk, Java, Python, Ruby) or script (E, Erlang). It would be very nice for efficiency reasons to be able to replace the referent behavior without altering the state, and if the become:/newtype rewrapping is guaranteed to be O(1), or even O(0), then it becomes the preferred way to do this replacement.

      2. 4

        It’s a powerful function, but the cost is enormous. You either need to stop all threads with access to that thread heap and basically run a full collection, or you need to make all message sends a double-hop through an intermediate table so it can be modified in-situ. Definitely not something you want to be doing as part of normal business in production.

        1. 6

          It’s much easier in Smalltalk than in other languages because Smalltalk has no non-private instance variables, so the only thing that you can do with an object is a message send.

          Generally, you implement this (if you don’t have an object table) by replacing the object’s isa pointer with a forwarding class and store the pointer to the new version in the old one (at any field address, it doesn’t matter because the other fields can’t be accessed). Then, when the forwarding class is invoked, you inspect the parent call frame, find references to the source object, and rewrite them to the point to the new one, and finally forward the method. If the sender sends another message to the same object, their new read of the pointer will point to the new version, so you don’t hit the thunk very often unless you have huge fan-in (which is rare in Smalltalk).

          You can do most of this in Objective-C (I have done), but if anything is directly accessing instance variables (even via reflection APIs) then it won’t observe the ‘become’ behaviour.

          1. 3

            Couldn’t you implement this using the same techniques as those used by concurrent, compacting garbage collectors, using read or write barriers along with forwarding pointers?

          2. 1

            The motivating example of lazy loading sounds like a thunk in haskell. Only evaluate it when needed.

            1. 1

              I would expect most of the languages support such kind of redefinitions. What’s the difference between this and a simple assignment?

              1. 2

                I think the main difference here is that this isn’t just simple swapping, as the article mentioned; in a become: b, the a object becomes the b object, in that all references that point to a over the entire program now point to b (and all references that point to b now point to a). So the object itself becomes something else.

              2. 1

                Shouldn’t this be easy to implement in a GCd language? indirect pointers are used so that one can move the objects around right?

                1. 1

                  Many (most?) modern GC’d languages and runtimes do not use indirect pointers.

                  1. 1

                    Aren’t indirect pointers required for compaction? how else do you reduce fragmentation?

                    1. 1

                      Not all GC’d languages use compaction - alternatively, you can use zoned/pooled allocators, etc.