[racket] struct-copy and custom-write bug?

From: Gustavo Massaccesi (gustavo at oma.org.ar)
Date: Sat Mar 1 13:03:47 EST 2014

This is not a problem with copy-struct. It's a weird combination of small bugs.

One of the problems is that the result of (struct-copy Base (Atom
3))) is a Base? struct, not a Atom? struct. More on this later.


First, let's analyze a reduced version of your program. Just delete
the copy-struct and all the Atom related parts.


#lang racket

(define (Print stx port mode)
  (write-string (Pair-cdr stx) port))

(struct Base ()
   #:methods gen:custom-write
   [(define write-proc Print)])

(struct Pair Base (car cdr))

(Pair-cdr (Base)) ;==> doesn't finish


The problem here is that (Pair-cdr (Base)) tries to raise an error,
because it's not a Pair. But to be helpful, the error tries to print
(Base), but Base has a gen:custom-write procedure that is Print. The
problem is that Print calls (Pair-cdr stx) [with stx = (Base)] that
raise an error ... So this never finish.

The first fix is in the Print procedure, to make it print plain Base struct's


(define (Print-Everything stx port mode)
  (cond
    [(Atom? stx) (write-string "ATOM " port)]
    [(Pair? stx) (Print-Everything (Pair-cdr stx) port mode)] ; and Pair-car ?
    [(Base? stx) (write-string "BASE-OR-UNKNOWN " port)]
    [else (error "not a BASE  :(")])) ; TODO: improve error message


(I changed the name. I think that instead of a big cond, it's better
to give each derived struct its own gen:custom-write procedure.)


The other problem is that struct-copy doesn't really copy all the
details of the derived struct. It's more like struct-copy/cast-to. For
example, the result of (struct-copy Base (Atom  3))) is a Base?
struct, not a Atom? struct.


AFAIK it's not possible to clone an arbitrary struct. (It's possible
in some cases like #:prefab or #:transparent struct's.
http://rosettacode.org/wiki/Polymorphic_copy#Racket)


One possible solution is to create a custom gen:custom-copy

(require racket/generic)

(define-generics custom-copy
    (struct-copy-x custom-copy)) ;TODO: add wellknown struct's

(define (Copy-Everything stx)
  (cond
    [(Atom? stx) (Atom (Atom-datum stx))]
    [(Pair? stx) (Pair (Pair-car stx) (Pair-cdr stx))]
    [(Base? stx) (Base)]
    [else (error "not a BASE  :(")])) ; TODO: improve error message


(Also, instead of a big cond, it's better to give each derived struct
its own gen:custom-copy.)


And now the example is


(struct Base ()
  #:methods gen:custom-write
  [(define write-proc Print-Everything)]
  #:methods gen:custom-copy
  [(define struct-copy-x Copy-Everything)])

(struct Atom Base (datum))

(struct Pair Base (car cdr))

(define t (struct-copy-x (Atom 3)))

t


;---
Gustavo

On Sat, Mar 1, 2014 at 7:50 AM, Jon Stenerson <jonstenerson at comcast.net> wrote:
> When I put the following in the DrRacket definitions window and then
> evaluate t, it works for a few seconds and runs out of memory. Anyone
> understand the problem?
>
> #lang racket
>
> (define (Print stx port mode)
>   (if (Atom? stx)
>       (write-string "ATOM " port)
>       (Print (Pair-cdr stx) port mode)))
>
> (struct Base ()
>   #:methods gen:custom-write
>   [(define write-proc Print)])
>
> (struct Atom Base (datum))
> (struct Pair Base (car cdr))
>
> (define t (struct-copy Base (Atom  3)))
> ____________________
>  Racket Users list:
>  http://lists.racket-lang.org/users

Posted on the users mailing list.