I understand how to write a function that takes an arbitrary number of arguments using the dot notation. Example: (define (func-name . args) func-body)
.
And I understand how to use a constructor guard to preprocess the constructors arguments, allowing me to pass different types to the constructor. Example:
(struct struct-id (field-ids)
#:guard (lambda (field-ids type-name) process-fields))
But that's as close as I can get. Can you write a guard that takes an arbitrary number of arguments? Or is there some other way to modify what a struct constructor does?
Although you can specify another name for the constructor with #:constructor-name
, in my experience that doesn't "free up" the struct identifier to be used as a function name:
(struct baz (a b c) #:transparent #:constructor-name -baz)
(-baz 1 2 3) ;(baz 1 2 3)
(define (baz a b c)
(-baz 1 2 3))
; module: duplicate definition for identifier
; at: baz
; in: (define-values (baz) (new-lambda (a b c) (-baz 1 2 3)))
So I usually just define an alternative constructor using another name, and use that instead of the default one (which is still available to use, if desired).
As for a macro to do this, plus supply keyword args and optional args: I wasn't able to get the macro in the other answer to work for me.
append*
-ing (flattening) the (#:kw id)
arg specs in the function definition.construct
instead of one formed from the user's struct identifier.loop
macro, pulled in from an old-style PLaneT package.Instead, what does work for me is modifying and extending the code I showed in a blog post, as follows:
#lang racket/base
(require (for-syntax racket/base
racket/list
racket/syntax
syntax/parse))
(begin-for-syntax
(define syntax->keyword (compose1 string->keyword symbol->string syntax->datum)))
(define-syntax (struct/kw stx)
(define-syntax-class field
(pattern id:id
#:with ctor-arg #`(#,(syntax->keyword #'id) id))
(pattern [id:id default:expr]
#:with ctor-arg #`(#,(syntax->keyword #'id) [id default])))
(syntax-parse stx
[(_ struct-id:id (field:field ...) opt ...)
(with-syntax ([ctor-id (format-id #'struct-id "~a/kw" #'struct-id)]
[((ctor-arg ...) ...) #'(field.ctor-arg ...)]) ;i.e. append*
#'(begin
(struct struct-id (field.id ...) opt ...)
(define (ctor-id ctor-arg ... ...) ;i.e. append*
(struct-id field.id ...))))]))
Example usage:
;; Define a struct type
(struct/kw foo (a b [c #f]) #:transparent)
;; Use normal ctor
(foo 1 2 3) ; => (foo 1 2 3)
;; Use keyword ctor
(foo/kw #:a 1 #:b 2 #:c 3) ; => (foo 1 2 3)
;; Use keyword ctor, taking advantage of default arg for #:c field
(foo/kw #:a 1 #:b 2) ; => (foo 1 2 #f)
Granted this is simplistic, it would need more work to support everything normal struct
can do.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With