[plt-scheme] serialization: transient fields

From: Jon Rafkind (workmin at ccs.neu.edu)
Date: Sat Aug 12 13:38:52 EDT 2006

Matthew Flatt wrote:
> At Wed, 09 Aug 2006 22:49:56 -0400, Jon Rafkind wrote:
>> I quickly realized this doesnt work with deserialization. The exact
>> problem, if it isn't obvious, seems to be the class body is never
>> evaluated and so any constructors or definitions are not called when the
>> object is deserialized.
> It's certainly true that constructors are not called by deserialization
> (except for instances of externalizable<%>, in which case the
> constructor is always called with no initialization arguments).
> I'm pretty sure that `define/transient' works like `transient' in Java.
> Maybe you want to perform some work at deserialization time to restore
> values in transient fields?
Serialization, as it is implemented now in class.ss/serialize.ss, works
the same as Java's serialization but in Java you can hook into the
serialization mechanism by defining readObject()
private void readObject( ObjectInputStream in ){
   callSomeMethod(); // object is already read in from the stream, do
some arbitrary processing

Whereas in mzscheme once serialization occurs you get an object and must
call a method after it is returned to you:
(send (deserialize stream) some-method)

Which isnt very convenient. What would be nice is if you could call a
default-internalize method as well:

(define (internalize v) (begin (default-internalize v) (some-method)))
>> I tried changing the transient definition so
>> that the internalize method would always create the transient class with
>> the proper value, that value passed as v to (define t-id v).
>>   (define-serializable-class* transient% object% (externalizable<%>)
>>     (init-field [val #f] [create-thunk (lambda () (void))])
>>     (define/public (externalize) #f)
>>     (define/public (internalize v) (create-thunk))
>>     (super-new))
> The `internalize' method is called on an object instantiated with no
> initialization arguments. Thus, `create-thunk' above lways has its
> default value when called via `internalize'.
> Any information that you want to communicate from serialization to
> deserialization must be in the result from `externalize', and then used
> via the argument to `internalize'. Also, the result from `externalize'
> must be serializable.
> So, you can create a variant of `transient%' that keeps an object
> reference instead of a closure. The `externalize' method could return
> that reference instead of #f. Finally, the `internalize' method could
> use the object, supplied as `v', to re-initialize `val'.
>   (define-serializable-class* transient% object% (externalizable<%>)
>     (init-field [val #f] [restorer #f])
>     (define/public (externalize) restorer)
>     (define/public (internalize v)
>       (set! restorer v)
>       (set! val (send restorer restore-value)))
>     (super-new))
Thats a nice idea but I was unable to implement it in a reasonable way.
In the end I abandonded serializing entire class's and just serialize
the x/y components since thats all I really cared about anyway.

(define/public (externalize)
  (list (get-field x this)
          (get-field y this)))

As for the dealing with transient member fields I call (constructor),
which all my classes have, in the `internalize' method of the root
class( the one with x/y in it ), so subclasses have a chance to
initialize fields.

Posted on the users mailing list.