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
>

Reply via email to