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.