Problem: I want to create a "smart constructor" for
struct (e.g., supporting default values for some fields) without losing the ability to use
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-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?