Hi folks,

I am becoming more converted to the way of structs vs hashes as time goes
on and so I sat down to write a macro that would generate functional
setters as a convenience.  Code is at bottom of email; I'd appreciate
suggestions on how to improve it, in particular better ways of handling
macros that have multiple optional parts, e.g.:

   (struct foo (bar baz jaz))
   (make-functional-setter foo bar)
   (make-functional-setter foo baz string?)
   (make-functional-setter foo jaz path-string? path-string->string)

I've seen this handled in two ways:

1) define-syntax-class with multiple (pattern ...) clauses, which renders
the parsing cleaner and easier to understand.  Handling missing parts is
still an issue, since using an attribute that wasn't matched is a syntax
error, so my questions:

A) Is there a way to test if a syntax class has a particular attribute
before trying to use it?

B) Alternatively, is there a way to create a null syntax object that
expands to nothing?  Not (void), not "", literally nothing.   Then I could
have each pattern bind all the attributes via #:with and just have some of
them be blank.


2) Alternatively, macros with optional parts can be handled by a series of
test cases in a syntax-parse, one for each option.  That ends up with a lot
of copy/pasted code between the segments.  I'm getting around this via
re-parsing but that feels clumsy.  What is the better way?


3) There's something I'm not understanding about providing this thing.  I
have it in a file called make_functional_setter.rkt:

#lang racket
(require (for-syntax racket/syntax syntax/parse))
(provide make-functional-setter)
; code and comments for make-functional-setter, see bottom of email

When I (require "../make_functional_setter.rkt") on the CLI racket shell,
everything works great.  As soon as I try to require it into a file, it
fails:

Contents of file test.rkt:

#lang racket
(struct bar (a b))
(require "../make_functional_setter.rkt")
(make-functional-setter bar a)
(make-functional-setter bar b path-string? ~a)

When I do "racket test.rkt" on the command line, I get the following
exception:

bar?: unbound identifier in module
  context...:
   #(2082 module test 0) #(3901 module) #(3902 module struct 0) #(25633
macro)
   #(25639 use-site) #(25818 local) #(25820 intdef)
  other binding...:
   #f
   #(2081 module) #(2082 module test 0)
  in: bar?
  context...:
   standard-module-name-resolver

I've been through this in the DrRacket macro stepper but that hasn't helped
me any.  (It rarely does.)

I'm assuming it's a phase issue, so I've played around with for-syntax,
for-template, and for-meta but apparently I do not understand phases well
enough.

What could be causing this?



I've been going through all the macro- and syntax-related docs that I can
find, but there's a lot of them and they take a few re-reads to sink in, so
there's probably something exactly for this that I haven't seen.

Code is below and at
https://gist.github.com/dstorrs/a53ec8736551eb5745d1d5fd265b5062 in case
Gmail mangled the formatting.


;;     make-functional-setter: macro for generating non-mutating field
;;     setter functions for a struct
;;
;; Define a struct:  (struct book (title current-page filepath)
#:transparent)
;;
;; Generate 'set-book-title', 'set-book-current-page', and
'set-book-filepath'.
;; All of these take two to four arguments: the 'book' struct to update,
the
;; new value, an optional contract, and an optional wrapper function.
;;
;;    (make-functional-setter book title)
;;    (make-functional-setter book current-page  exact-positive-integer?)
;;    (make-functional-setter book filepath      path-string?
 path-string->string)
;;
;; Details:
;;    set-book-title           accepts any value, regardless of sensibility
;;    set-book-current-page    accepts only exact-positive-integer?s, else
contract violation
;;    set-book-filepath        accepts only path-string?s, converts to
string before storing
;;
;; Examples:
;;    (define b (book "Foundation" 297 (build-path "/foo/bar")))
;;    b                                                ; (book "Foundation"
297 "/foo/bar")
;;    (set-book-title b (hash))                        ; (book (hash) 297
"/foo/bar")
;;    (set-book-current-page b 99)                     ; (book "Foundation"
99 "/foo/bar")
;;    (set-book-current-page b 'x)                     ; ERROR!  Contract
violation
;;    (set-book-filepath b (build-path "/foo"))        ; (book "Foundation"
297 "/foo")
;;
(define-syntax (make-functional-setter stx)
  (syntax-parse stx
    ; First, grab the name of the struct and the field we're making
    ; this for.  We'll build some stuff here then re-parse instead of
    ; copy/pasting for every pattern match
    [(_ type-name field-name ignored ...)
     (with-syntax* ([func-name   (format-id #'type-name "set-~a-~a"
#'type-name #'field-name)]
                    [func-header #'(func-name the-struct val)]
                    [definer     #'define]
                    [type-pred   (format-id #'type-pred "~a?" #'type-name)]
                    [func-body   #'(struct-copy type-name the-struct
[field-name val])]
                    )
       (syntax-parse stx
         [(_ _ _) #'(definer func-header func-body)]
         [(_ _ _ field-contract:expr ignored ...)
          (with-syntax ([definer #'define/contract]
                        [func-contract #'(-> type-pred field-contract
type-pred)])
            (syntax-parse stx
              [(_ _ _ _) #'(definer func-header func-contract func-body)]
              [(_ _ _ _ wrapper:expr)
               #'(definer func-header
                   func-contract
                   (struct-copy type-name the-struct [field-name (wrapper
val)]))]))]))]))

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to