On 6 March 2012 22:22, Eliot Miranda <[email protected]> wrote: > Hi Frank, > > On Tue, Mar 6, 2012 at 1:49 PM, Frank Shearar <[email protected]> > wrote: >> >> On 6 March 2012 18:27, Eliot Miranda <[email protected]> wrote: >> > >> > >> > On Tue, Mar 6, 2012 at 3:05 AM, Henrik Johansen >> > <[email protected]> wrote: >> >> >> >> >> >> On Mar 6, 2012, at 12:03 PM, Henrik Johansen wrote: >> >> >> >> >> >> >> > >> >> > Parcels >> >> > - Check a hash based on class layout vs equivalent hash for the >> >> > stored >> >> > method's class (stored with the method). >> >> > If different: >> >> > 1) check for existence of a backwards-compatability reader method, >> >> > hand >> >> > the old instance to it, and expect the old instance to be #become'd >> >> > into >> >> > something current, >> >> > 2) raise an error if no such method exists. >> >> > >> >> > Now, the main problem with this scheme is it's left as an exercise to >> >> > the user to come up with a procedure to ensure said backwards-compat >> >> > reader >> >> > method stays up to date as additional changes are made. >> >> > >> >> > Is it a big problem? >> >> > Depends on your tests, and programmer diligence at any given day. >> >> > >> >> > Added inst vars are not a big deal, as if you forget them, there will >> >> > usually be a nil #DNU somewhere down the line. (or you use lazy >> >> > initialization, and everything works as expected even for existing >> >> > instances) >> >> > >> >> > Removals/reorderings are a bigger issue, as the detection of failure >> >> > (inst vars in wrong slots) are often far removed from the source of >> >> > problems >> >> > (forgetting to update the reader method), >> >> > If seldomly used, it may even go unnoticed until the instance is next >> >> > saved, at which time you're in real trouble (especially if saved >> >> > alongside >> >> > newly created ones, in which case there is no consistency). >> >> > >> >> > In general I think it's an ok scheme, but would like to see a >> >> > solution >> >> > more resilient to user-error. >> >> > How to achieve that is an interesting topic, which I haven't yet >> >> > found >> >> > time to think through as thoroughly as I had wished/intended some >> >> > months >> >> > ago. :( >> >> >> >> Errr, BOSS, not Parcels. >> > >> > >> > Right. What Parcels do is shape-change instances *and* rescan methods to >> > fix >> > up inst var offsets. When a parcel is saved the "signature" of classes >> > of >> > instances there-in are saved so that the parcel contains all the inst >> > var >> > names of the class and its superclass. If on load the superclass chain >> > has >> > a different set of inst vars then lost inst vars are omitted and added >> > inst >> > vars are nil in the materialized instances. If a Parcel contains a full >> > class (not just instances) that has methods, and the superclass chains >> > inst >> > vars have changed then these methods are "rescanned" (disassembled, and >> > reassembled, not using the compiler) and inst var offsets are fixed up, >> > missing inst vars getting changed into undeclared variable references (I >> > think; at least this is what *should* happen; its what happens when one >> > removes an inst var that is still referenced from methods). >> > >> > What's missing in the parcel scheme is any hook to allow the user to >> > process >> > shape-changing instances with the values of lost inst vars in hand. >> > Presumably there will be cases when those values are essential to any >> > schema migration. Personally I would want to decouple schema migration >> > and >> > add it as a post-processing step, which would imply that the >> > materializer >> > would offer a service (materialize in a special mode) where it >> > constructed a >> > dictionary from materialized and shape-changed instance to in parcel >> > state >> > (e.g. an Array of inst var values as they occurred in the parcel). Then >> > the >> > materialized instance could be migrated after the fact, with the default >> > behaviour being analogous to what the system does now on class >> > redefinition >> > (values of deleted inst vars are lost, aded inst vars are nil). >> >> This sounds a bit like CLOS' UPDATE-INSTANCE-FOR-REDEFINED-CLASS [1], >> which is a generic function (for our purposes just think "something a >> bit like a method") that you define and is invoked by the machinery >> involved in changing/redefining classes. >> >> A colleague told me about this part of CLOS the other day when >> discussing class-based OO: it's the sort of thing we need in at least >> the Browser, so one can change the shape of existing instances and >> ensure the new class invariants are maintained. > > > But Smalltalk has done without this for 40 years. It has supported existing > instance redefinition but no-one has felt the need to provide a scheme for > redefinition in the IDE. In any case it is relatively trivial to script it, > e.g.: > > | instanceState | > instanceState := IdentityDictionary new. > myClass allInstancesDo: [:i| instanceState at: i put: ((1 > to: myClass instSize) collect: [:ivi| i instVarAt: ivi])]. > myClass superclass subclass: myClass name instanceVariableNames: myClass > instVarNamesString, ' theExtraInstVar' [...]. > myClass compile: sourceForupdateToNewFormatFrom. > myClass allInstancesDo: [:i| i updateToNewFormatFrom: (instanceState at: i] > > So if and when you need it you can roll your own (and I've never needed it; > but I have wanted the refactoring browser to preserve inst vars on push > up/push dwn).
Indeed, it is trivial. Using #allInstancesDo: is exactly what I used when my colleague and I were discussing the issue. The only difference is that _I_ did the migration, not my tools. > However, collections of persistent objects are a different kettle of fish. > It is much more likely to need instance migration on load, especially for > library objects that the user is merely using and whose implementation has > evolved over time (e.g. UI elements such as morphs). > >> >> (For instance, for >> "purely functional" objects, you really don't want to use reflection >> to initialise new instvars from outside the object.) > > > But the class builder uses an unprotected interface (instVarAt: and > instVarAt:put:). So this turns out not to be an issue. That's exactly to what I was referring: I lump #instVarAt: in the reflection bucket. I realise its _utility_, but it's strong medicine. Which is pretty much why you're pushing mirrors, right? So that some things - the class builder, as an example - can make use of such a (powerful and dangerous) tool while some random library can't. frank >> So you'd change >> the class definition, supply a way of handling the schema migration, >> and then the image would change the class definition and run the >> migration. >> >> frank >> >> [1] http://clhs.lisp.se/Body/f_upda_1.htm >> >> > This reminds me of a bug with the ClassBuilder/RefactoringBrowser >> > combination. If one refactors pushing an inst var up to a superclass or >> > down to subclasses, the values of te inst var in instances are lost >> > because >> > the class change is in fact done as two changes, a deletion followed by >> > an >> > addition. This again could be fixed in a wrapper, building a dictionary >> > from instance to values, performing the set of class changes, and then >> > restoring the inst var state from the dictionary. >> > >> > >> >> >> >> My bad. >> >> >> >> Cheers, >> >> Henry >> >> >> >> >> > >> > >> > >> > -- >> > best, >> > Eliot >> > >> > > > > -- > best, > Eliot >
