[gambit-list] structure -> sexp

Marc Feeley feeley at IRO.UMontreal.CA
Sun Dec 5 11:48:01 EST 2004


> Howdy!
> 
> Is there an easy way to convert a structure to scheme code (sexp) that
> can by restored by evaluating it? I'm looking for something like this:
> 
> http://schemecookbook.org/Cookbook/StructuresReadingAndWriting

I can think of two ways to achieve this.  The one I prefer is using
the built-in serialization feature of Gambit-C.  Here's some sample
code:

; using the built-in serialization feature

(define (serialize obj)
  (call-with-output-string
   '()
   (lambda (p)
     (output-port-readtable-set!
      p
      (readtable-sharing-allowed?-set
       (output-port-readtable p)
       'serialize))
     (write obj p))))

(define (deserialize str)
  (call-with-input-string
   str
   (lambda (p)
     (input-port-readtable-set!
      p
      (readtable-sharing-allowed?-set
       (input-port-readtable p)
       'serialize))
     (read p))))

(define-type date
  id: 836BAA77-46D5-11D9-B4D1-00039301BA52
  day
  month
  year
)

(define-type person
  id: 83C73CCE-46D5-11D9-9982-00039301BA52
  name
  birthdate
)

(define p (make-person "Joe Smith" (make-date 5 12 2004)))

(define s (serialize p))

(display "\nstructure:\n") (pp p)

(display "\nserialized:\n") (pp s)

(display "\ndeserialized:\n") (pp (deserialize s))

; Here's the output generated:
;
;structure:
;#<person #2
;         name: "Joe Smith"
;         birthdate: #<date #3 day: 5 month: 12 year: 2004>>
;
;serialized:
;"#structure(#structure(#0=#structure(#0# ##type-6 type 8 #f #(id 1 #f name 5 #f flags 5 #f super 5 #f fields 5 #f cfields 5 #f) #(1 2 3 4 5 6)) ##type-2-83C73CCE-46D5-11D9-9982-00039301BA52 person 8 #f #(name 0 #f birthdate 0 #f) #(1 2)) \"Joe Smith\" #structure(#structure(#0# ##type-3-836BAA77-46D5-11D9-B4D1-00039301BA52 date 8 #f #(day 0 #f month 0 #f year 0 #f) #(1 2 3)) 5 12 2004))"
;
;deserialized:
;#<person #4
;         name: "Joe Smith"
;         birthdate: #<date #5 day: 5 month: 12 year: 2004>>

Notice that the structures are given a "unique id", which allows the
system to recover the correct type of a deserialized object (in the
sense that the type predicate and accessors will continue to work
properly).  It also allows these structures to be communicated to a
Scheme process (server, client, etc) that knows nothing about this
type.  That process can store these structures (but not access the
content), and then send them back to a process that knows about this
type (like the originating process) which can access the content.
Another advantage of this method is that you can serialize functions
and continuations.  I'm not sure you need this for your program but
maybe you'll find a use for this in the future.

The main problem with this method is that the serialized
representation takes quite a bit of space (because it contains all the
information of the type and the supertypes, such as the name of the
type, the name of the fields, the attributes of the fields, the unique
identifier, etc).  I don't think this is a problem for your
application because objects are small and over a network there is
little difference between sending a 40 byte packet and a 400 byte
packet.  Moreover, if you are sending a long list of objects the type
information will be included only once in the serialized
representation.

The second method is closer in spirit to the web link you mention.  It
consists of an explicit encoding/decoding of the structures into
vectors with a special first element that indicates the type of the
structure.  Here's some sample code:

; implementation of read/write structures

(define-macro (define-read-write-type name . definition)
  `(begin
     (define-type ,name , at definition)
     (register-constructor
      ',name
      ,(string->symbol
        (string-append "make-" (symbol->string name))))))

(define (constructor-id name)
  (string->symbol
   (string-append "<STRUCTURE>" (symbol->string name))))

(define constructor-registry '())

(define (register-constructor name constructor)
  (set! constructor-registry
        (cons (cons (constructor-id name) constructor)
              constructor-registry)))

(define (encode obj)
  (cond ((##structure? obj)
         (let ((type (##structure-type obj)))
           (list->vector
            (cons (constructor-id (##type-name type))
                  (map encode (cdr (##vector->list obj)))))))
        ((pair? obj)
         (cons (encode (car obj))
               (encode (cdr obj))))
        ((vector? obj)
         (list->vector (map encode (vector->list obj))))
        ((or (number? obj)
             (string? obj)
             (boolean? obj)
             (null? obj))
         obj)
        (else
         (error "can't encode this object" obj))))

(define (decode obj)
  (cond ((pair? obj)
         (cons (decode (car obj))
               (decode (cdr obj))))
        ((vector? obj)
         (cond ((and (>= (vector-length obj) 1)
                     (assq (vector-ref obj 0) constructor-registry))
                =>
                (lambda (c)
                  (apply (cdr c) (map decode (cdr (vector->list obj))))))
               (else
                (list->vector (map decode (vector->list obj))))))
        ((or (number? obj)
             (string? obj)
             (boolean? obj)
             (null? obj))
         obj)
        (else
         (error "can't decode this object" obj))))

; a test:

(define-read-write-type date
  day
  month
  year
)

(define-read-write-type person
  name
  birthdate
)

(define p (make-person "Joe Smith" (make-date 5 12 2004)))

(define e (encode p))

(display "\nstructure:\n") (pp p)

(display "\nencoded:\n") (pp e)

(display "\ndecoded:\n") (pp (decode e))

; Here's the output generated:
;
;structure:
;#<person #6
;         name: "Joe Smith"
;         birthdate: #<date #7 day: 5 month: 12 year: 2004>>
;
;encoded:
;#(<STRUCTURE>person "Joe Smith" #(<STRUCTURE>date 5 12 2004))
;
;decoded:
;#<person #8
;         name: "Joe Smith"
;         birthdate: #<date #9 day: 5 month: 12 year: 2004>>

Hope this helps.

Marc


More information about the Gambit-list mailing list