New struct++ feature: accessor wrappers

Accessor wrappers allow you to redirect a struct accessor to do whatever you want. Good examples would be to force a promise or access a database.

#lang racket

(require struct-plus-plus "db/dbh.rkt") ; dbh.rkt provides (dbh) which returns a pre-initialized database connection

(struct++ dog ([name (or/c string? promise?) #:wrap-accessor (wrap-accessor force)]))
(define fido (dog++ #:name (lazy "fido")))
(displayln
 (~a " (dog-name fido), the default struct accessor, returns: " (dog-name fido)
     "\n before dotted accessor, promise was forced?: " (promise-forced? (dog-name fido))
     "\n (dog.name fido), the struct++ dotted accessor, returns: " (dog.name fido)
     "\n after dotted accessor, promise was forced?: " (promise-forced? (dog-name fido))))


; a trivial ORM
(struct++ person ([db-id         integer?]
                  [(conn (dbh))  connection?] ; default argument creates the connection
                  [(username #f) string?  #:wrap-accessor  (lambda (the-person val) (get-username the-person))]
                  [(go-boom #f)  integer? #:wrap-accessor  (lambda (the-person val) (get-username the-person))]
                  )
          #:transparent)

(define (get-username the-person)
  (query-value (person.conn the-person)
               "select username from users where id = ?"
               (person.db-id the-person)))


(define smith (person++ #:db-id 2))
(displayln (~a "(person.username smith) returns: " (person.username smith)))
(display (~a "(person.go-boom smith) dies because the returned value does not match the field contract: "))
(person.go-boom smith)

The output is:

 (dog-name fido), the default struct accessor, returns: #<promise!fido>
 before dotted accessor, promise was forced?: #f
 (dog.name fido), the struct++ dotted accessor, returns: fido
 after dotted accessor, promise was forced?: #t
(person.username smith) returns: smith@ursaminor.edu
(person.go-boom smith) dies because the returned value does not match the field contract:
; person.go-boom: broke its own contract                                                                      
;   promised: integer?                                                                                        
;   produced: "smith@ursaminor.edu"                                                                           
;   in: the range of                                                                                          
;       (-> person? integer?)                                                                                 
;   contract from: (function person.go-boom)                                                                  
;   blaming: (function person.go-boom)                                                                        
;    (assuming the contract is correct)                                                                       
;   at: /Users/dstorrs/test.rkt                                                                  
; Context (plain; to see better errortrace context, re-run with C-u prefix):                                  
;   /Applications/Racket_v8.4/collects/racket/contract/private/blame.rkt:346:0 raise-blame-error              
;   /Applications/Racket_v8.4/collects/racket/contract/private/arrow-higher-order.rkt:375:33    

They come with one caveat which is that they can return a result different from what's actually there, in which case the wrapped accessor would give a different result than you would get from, e.g., match.

The fact that the default accessors created by struct do not use the wrapper is a feature, not a bug.