My question is the same as the one asked here, but the answer given there doesn't actually work.
I have a bunch of structs inheriting from a parent struct A
, which has two fields that I want to make optional for all its descendants. Previously I was using #:auto
, but that turns out to really not be what I want because it breaks methods like struct-copy
, and also I do want to be able to optionally supply values for those fields on struct creation.
I've found a few other questions about optional arguments for structs, but the answers have all suggested defining a custom constructor and using that instead. Unfortunately I already have a lot of code using the regular constructors, so what I really want is for this custom constructor to have the same name as the struct itself. The answer to the question I linked was what I was looking for, but unfortunately it only works in the REPL because there duplicate definitions are allowed. Outside of the REPL I get an error like module: duplicate definition for identifier in: exn:my-app
, for example (when running the answer to the linked question).
edit: I know the duplicate definition problem is because the struct id is also bound to a transformer. I don't want to stop that definition from happening; I want a name bound to both a constructor and a transformer for that struct, where the constructor is not the default one.
Is there a way to do this that will play nicely with existing code?
Looking further into the new struct keywords that soegaard mentioned, I think I've come up with a solution that's a bit cleaner and, more importantly, much easier to abstract into a macro (those modules and requires were giving me a really hard time). I thought I'd share it for future reference. Again, this requires one of the nightly Racket builds. I'm using 6.5.0.7.
(Note: the use of case-lambda
over defining keyword arguments here is just my preference.)
#lang racket
(require (for-syntax syntax/transformer))
(struct horse (color)
#:name Horse
#:constructor-name Horse
#:transparent)
(define make-horse
(case-lambda
[() (Horse "black")]
[(color) (Horse color)]))
(define-match-expander horse
; match expander
(λ (pat)
(syntax-case pat ()
[(_ more ...) #'(Horse more ...)]))
; constructor
; edit: changing this to use make-variable-like-transformer,
; as suggested in the comments.
#;(syntax-id-rules ()
[(_ args ...) (make-horse args ...)]
[horse Horse])
(make-variable-like-transformer #'make-horse))
Sample usage:
> (define black-beauty (horse))
> black-beauty
(horse "black")
> (define ginger (horse "red"))
> ginger
(horse "red")
> (match black-beauty
[(horse color) color])
"black"
> (match ginger
[(horse color) color])
"red"
> (horse-color black-beauty)
"black"
> (horse-color ginger)
"red"
In short: instantiation, matching, and accessing fields seem to work as you would expect if you'd just used struct
. The struct id can also be used as an identifier without any syntax issues. I don't really think that part has much practical use, but I thought it was nice.
The struct
construct will define two entities with the name of the struct. A constructor and a transformer binding that has information on the struct.
In order to avoid the "duplicate identifier" error you can use #:omit-define-syntaxes
.
An alternative is to define the struct in a submodule and export only the things you need (and possibly renaming some of the identifiers).
#lang racket
(struct horse (color)
#:constructor-name make-horse
#:omit-define-syntaxes
#:transparent)
(define (horse #:color [color "black"])
(make-horse color))
(horse)
(horse #:color "red")
Output:
(horse "black")
(horse "red")
EDIT
A solution that works with match is possible with the help of a match expander.
Note: You need at least version 6.5 for this to work due the the use of #:extra-name
.
#lang racket
(module horse racket
(provide (struct-out Horse)
make-horse)
(struct horse (color)
#:extra-name Horse
#:extra-constructor-name make-horse
#:transparent))
(require 'horse)
; the custom horse constructor
(define (my-make-horse #:color [color "black"])
(make-horse color))
(define-match-expander horse
; match expander
(λ (pat)
(syntax-case pat ()
[(_horse more ...)
#'(Horse more ...)]))
; constructor
(λ (stx)
(syntax-case stx ()
[(_horse arg ...)
(syntax/loc stx
(my-make-horse arg ...))])))
(horse)
(horse #:color "red")
(match (horse #:color "blue")
[(horse color) color])
Output:
(horse "black")
(horse "red")
"blue"
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