[HN Gopher] The miracle of Smalltalk's become: (2009)
___________________________________________________________________
The miracle of Smalltalk's become: (2009)
Author : nathell
Score : 81 points
Date : 2022-11-23 09:00 UTC (14 hours ago)
(HTM) web link (gbracha.blogspot.com)
(TXT) w3m dump (gbracha.blogspot.com)
| andai wrote:
| Object.prototype.become = function(target) {
| const newProto = Object.getPrototypeOf(target);
| Object.setPrototypeOf(this, newProto);
| for ( let key of Object.keys(this) ) { delete
| this[key]; } Object.assign(this,
| target); }
|
| Here's as close as I could get in JS. This fails Object.is(a,b)
| though, it just makes object A's data the same as B's.
|
| The two objects will desynchronize, _unless_ all their properties
| are Objects (eg. Arrays) and no new ones are added.
|
| As far as I know there's no way to update all references to an
| object to point to a new object. Though you could take the
| article's advice and add a bunch of indirection with getters and
| setters, referencing an internal "true" object which could be
| swapped out trivially.
| cxr wrote:
| Getters and setters will also desynchronize for similar reasons
| (new properties being added/removed).
|
| In JS you're better served by proxies or to set things up ahead
| of time so you're not passing a direct object reference, but a
| mirror instead.
|
| <https://bracha.org/mirrors.pdf>
|
| Once you've got all relevant consumers using a mirror or a
| proxy and all access is therefore mediated, that's when you can
| freely perform these kind of swaps.
|
| As noted by the commenters, however, this is quite a
| heavyweight solution.
|
| Related:
|
| > _Inside the engine, a clever trick from Smalltalk called
| `becomes` is used to swap a newborn Proxy and an existing
| object that has arbitrarily many live references. Thus an
| object requiring no behavioral intercession can avoid the
| overhead of traps until it escapes from a same-origin or same-
| thread context, and only if it does escape through a barrier
| will it become a trapping Proxy whose handler accesses the
| original object after performing access control checks or
| mutual exclusion._
|
| > _The local jargon for such object /Proxy swapping is "brain
| transplants"._
|
| <https://brendaneich.com/2010/11/proxy-inception/>
|
| <https://bugzilla.mozilla.org/show_bug.cgi?id=580128>
| karmakaze wrote:
| > Take a minute to internalize this; you might misunderstand it
| as something trivial. This is not about swapping two variables -
| it is literally about one object becoming another. I am not aware
| of any other language that has this feature. It is a feature of
| enormous power - and danger.
|
| I do think implementing or understanding this is trivial.
| 'Become' can be implemented by 1. swapping all references to 'a'
| and 'b', or 2. swapping the data at memory locations of 'a' and
| 'b'. I can certainly see its interesting uses but don't see any
| value in describing it as a miracle or something that's hard to
| comprehend. Using the word 'become' to explain the language
| feature 'become' also isn't great.
|
| I used ObjectStore at one company that does 'pointer swizzling'
| to lazy load deserialized objects, where segfault takes the place
| of `doesNotUnderstand`.
| pjmlp wrote:
| I usually use it as an example when people say Python is too
| dynamic to have a JIT, when Smalltalk was one of the percusors
| of JIT for dynamic languages with features like become: (SELF
| then took it even further).
| Qem wrote:
| It's really unnerving when people repeat that "too dynamic"
| remark when there is widely known counterexamples.
| Fortunately it seems now people stopped denying the problem
| with the faster cpython project. IIRC, I think the roadmap
| for 3.12 or 3.13 already has some form of lightweight JIT.
| pjmlp wrote:
| Yes, apparently it took Microsoft's persuasion to make it
| happen, hiring Guido exactly for that purpose.
| taeric wrote:
| If I'm not mistaken, this is also an amusing retort to the
| idea that only statically typed languages can have common
| refactoring tools.
| pjmlp wrote:
| Specially since the first IDEs with such features were for
| Smalltalk and Lisp.
|
| Mesa (XDE) and then Mesa/Cedar, have several references
| Xerox papers, on how those enviroments served as
| inspiration for their IDE like features, like REPL, typo
| corrections, debugging, code reloading,...
| taeric wrote:
| Exactly. Too many folks take "static typing" as the only
| form of static analysis that can be done. Sometimes the
| only analysis that can be done on a codebase.
| zulu-inuoe wrote:
| Closest equivalent that I can think of is the change-class
| function in CL. That one also happens to be generic so you can
| control the finer details of what it means to change A to B
| c-smile wrote:
| In fact in most cases dynamic subclassing is enough for such
| things. In JavaScript that's achieved by changing __proto__
| reference on an object.
|
| So "obj instanceof A" becomes "obj instanceof B".
|
| As of persistence case I've solved it on JavaScript internal
| implementation level. In Sciter there is built-in JSON-ish data
| persistence module - close to Mongo-DB on feature set (modulo
| sharding).
|
| Storage loads objects as half-backed proxies that contain only db
| references. Only when code tries to access props/methods of the
| loaded object it gets fetched from disk, its __proto__ is set to
| particular class, etc.
|
| More on this architecture: https://gitlab.com/sciter-
| engine/sciter-js-sdk/-/blob/main/d...
|
| By the way, dynamic subclassing is not a prerogative of only
| dynamic languages, here is how I did that in C++:
| https://stackoverflow.com/questions/21212379/changing-vtbl-o...
|
| Patched QuickJS with storage support is here:
| https://gitlab.com/c-smile/quickjspp - it uses DyBase of
| Konstantin Knizhnik as a storage.
| kazinator wrote:
| Not really, because the goal is that obj is replaced by a
| different instance, not to change its type.
|
| Become can swap two objects that are exactly of the same type,
| where the prototype change would be a null operation, achieving
| nothing.
| c-smile wrote:
| "obj is replaced by a different instance"
|
| Could you provide real life scenario when you will need that?
|
| I mean to change all references to the object in the heap at
| runtime ...
| pjmlp wrote:
| Live code editing on Smalltalk debugger, after changes on
| the class browser, redoing statement that caused the break
| into debugger, and have all live instances on the image
| updated.
| atemerev wrote:
| "become" is a standard approach in Erlang, Akka and other actor
| model systems (because it is included in the original actor model
| axioms by Carl Hewitt, "[a message can] designate the behavior to
| be used for the next message it receives"). We write state
| machines with that (on some events, you just 'become' another
| state with its own reactions to messages).
| masklinn wrote:
| It helps a lot that there's limited way to inspect an actor in
| a way which is not mediated by the actor itself, so for
| starters you wouldn't bother, and even if you did the
| "replacement" actor can just reply in a way you'd expected.
| AustinDev wrote:
| True become false
| kazinator wrote:
| > _In the absence of an object table, become: traverses the heap
| in a manner similar to a garbage collector. The more memory you
| have, the more expensive become: becomes._
|
| Well, yes, that should be "the larger the reachable object graph
| you have, the more expensive become: becomes". It's not necessary
| to do the substitution in areas of the heap that are garbage.
|
| I would rather do it without any sort of object table, because
| then you can do things like make some string object become the
| fixnum 42 everywhere. :)
|
| If you're doing something with object persistence, you can't be
| doing individual become operations object-by-object. There has to
| be a batch API for doing a mass become where you pass a
| dictionary of what is to become what, and the objects are
| traversed once to do all the rewrites.
| dingosity wrote:
| The magic here comes from Smalltalk's object memory system, which
| makes this object easy. (Well.. okay.. not completely easy, but
| simple.)
|
| Also... I know in Digitalk, you couldn't do a become: on
| SmallInteger because of the way their object memory worked, but
| don't remember if this was a limitation of SmallTalk-80 or
| Squeak.
|
| Which is to say... when you have time, if you haven't done it
| already, do a search on "smalltalk object memory" or "Loom" or
| "bluebook object memory." There are some great references from
| the dawn of time.
| thecodedmessage wrote:
| Squeak implements it with horrible performance, IIRC
| heisenbit wrote:
| Combining ,become:' with the ability to serialize objects was
| fun.
| yccs27 wrote:
| This would've been very convenient for lazy loading in a recent
| python project. I ended up making the husk turn into a
| transparent wrapper for the loaded object, unfortunately adding a
| layer of indirection to every subsequent call. Does anyone here
| know a better way to do lazy loading objects in python?
| masklinn wrote:
| If all the types are the same shape (at the C level) you can
| swap the class and all attributes of the husk.
| BiteCode_dev wrote:
| Use ChainMap in __dict__
| yccs27 wrote:
| That works? Wow, I need to try this.
| BiteCode_dev wrote:
| I got carried away: you can use ChainMap, but the
| __getitem__ is not going to get called:
| https://bugs.python.org/issue1475692
|
| So your only valid solution is to use __getattr__, which is
| guaranteed to work. But either you load all missing value
| on first access, and you can only lazy load once, or you
| pay the price of a method call for attribute access every
| single time. And in all cases, you won't have type hint
| doing the ork for you.
|
| An hybrid strategy would be to always use __dict__ pointing
| to an empty dict you lazy load later, and @property for
| attribute that are only for the local object. Then you will
| pay the price of the of the method calls only for non lazy
| attributes.
|
| Depending of your work load and data shape, one solution
| will be much better than the other, but nothing as elegant
| as the ChainMap.
___________________________________________________________________
(page generated 2022-11-23 23:02 UTC)