Problem: I want to create a "smart constructor" for struct
(e.g., supporting default values for some fields) without losing the ability to use struct-copy
, struct*
in match
. In general, I don't want to lose the struct-info
information in the transformer binding.
One possibility is to duplicate what struct
is doing, but use my own smart constructor instead. This is a huge amount of work, and when struct
from Racket changes, my implementation would need to sync up to be compatible, putting even more burden on me.
I have another solution that looks really cursed, but appears to do its job well: impersonating the syntax transformer generated by struct
. Is it actually cursed? Does anyone have a better solution?
Here's a concrete example:
#lang racket
(module provider racket
(require syntax/parse/define
(for-syntax racket/struct-info))
(provide (rename-out [node* node]))
(struct node (id value) #:transparent)
(define-syntax-parse-rule (repack x:id constr new-id:id)
(define-syntax new-id
(impersonate-procedure
(syntax-local-value #'x)
(λ (y)
(values (syntax-parser [(_ . args) #'(constr . args)]
[_:id #'constr])
y)))))
(define better-node
(procedure-rename
(λ (id #:value [value #f])
(node id value))
'node))
(repack node better-node node*))
(require 'provider
(for-syntax racket/struct-info))
node
(match (node 1)
[(node id val) (println (list id val))])
(match (node 1 #:value 2)
[(node id val) (println (list id val))])
(println (struct-copy node (node 1) [value 10]))
(define-syntax (get-field-names stx)
#`'#,(struct-field-info-list (syntax-local-value #'node)))
(println (get-field-names))
In this exploit, I simply copy the content in the transformer binding entirely, so I get the support in struct*
, struct-copy
, etc. for free. The only change I made is that I impersonate the procedure so that it does something else. The syntax transformer originally expands to the use of the non-smart constructor, so I simply replace that with a syntax transformer that expands to the use of my smart constructor.
It looks almost perfect. I can give the original syntax transformer (i.e. (syntax-local-value #'x)
) any input (by adjusting y
) and can totally replace the output of the original syntax transformer with anything (by adjusting what goes to the syntax-parser
). This essentially allows me to supplant the original syntax transformer with anything that I want. The only constraint is that the original syntax transformer must still be called and must return without an error. An original syntax transformer that always errors, for instance, would ruin this scheme.
So for this particular syntax transformer generated by struct
, the exploit works well. But in general, it might not. Is there a solution to this? To put it another way, given a struct that may already have prop:procedure
and a procedure, is there a way to replace prop:procedure
with the procedure without the caveat I mentioned above?