Just write a wrapper:
(struct struct-id (a b c d) #:constructor-name struct-id*
#:guard (lambda (a b c d type-name) do-stuff))
(define (struct-id (a) (b) (c) (d 'default-value))
(struct-id* a b c d))
That gives you a constructor in which all the field arguments are optional. Defining them this way instead of with dot notation saves you from having to parse through the rest-argument.
I supplied a default value for d
, and Racket will make the default value of the others #f
.
You can also define it to have keyword arguments:
(define (struct-id #:a (a #f) #:b (b #f) #:c c #:d (d 'default))
(struct-id* a b c d))
In the above case, #:c
is a required argument because I left off the parentheses, I provided 'default
as the default value of d
, and the others will have a default value of #f
, which this time has to be explicitly provided. Keywords can be passed to the constructor in any order.
If you're using a lot of structs, you might want a macro to define the wrapper for you:
(begin-for-syntax
(require (planet jphelps/loop)) ;; Installs a library on first use. Be patient.
(define stx-symbol->string (compose symbol->string syntax->datum))
(define (make-constructor-name stx-name)
(datum->syntax stx-name
(string->symbol
(string-append (stx-symbol->string stx-name) "*"))))
(define (stx-symbol->stx-keyword stx-symbol)
(datum->syntax stx-symbol
(string->keyword
(symbol->string
(syntax->datum stx-symbol))))))
(define-syntax struct*
(lambda (stx)
(syntax-case stx ()
((_ struct-name fields . options)
#`(begin
(struct struct-name fields . options)
(define (#,(make-constructor-name #'struct-name)
. #,(loop for name in (syntax-e #'fields)
collect (stx-symbol->stx-keyword name)
collect #`(#,name #f)))
(struct-name . fields)))))))
Then define your structs like this:
(struct* struct-id (a b c d) #:guard whatever)
You'll automatically get a keyword-based constructor named struct-id*
that doesn't conflict with names that are generated by the struct
form.
EDIT
Apparently the above macro as it was originally written didn't work in module-based programs. I only tested it at the REPL, which behaves more like a Lisp in that you're allowed to redefine things. This masked the fact that struct
's #:constructor-name
option adds and additional constructor name instead of overriding the existing constructor name. This is in spite of the fact that there's an #:extra-constructor-name
option that also creates an additional constructor name.
Fixing this problem in a way that would be completely seamless would require you to reimplement the entire struct
macro. You'd have to rename the struct and then generate not only the constructor, but all of the accessors and mutators. An easier workaround would be to generate a constructor with a different name from the original constructor. I have edited the code above to implement this workaround.
#:constructor-name
. That's very helpful. – Cloak