This needs to be updated only if/when you need the new ssn field. So although it’s more verbose, using struct* is more resilient.

We could reduce the verbosity, by allowing either [field pat] or just field — where the latter expands to use the same symbol for both the field and pattern, as we wrote out in the example above. This would be a nice enhancement to the official struct* in racket/match. Meanwhile here’s a struct** match expander that wraps struct* to do so:

Then creating the struct is itself error-prone. You will probably start jotting down comments to help you keep track of what field you’re on:

1
2
3
4
5
6
7
8

(foo10;a"foo";b13;c"bar";d"baz";e#f;f"x";g42);h

It would help if we could turn those comments into actual keywords. Using keyword arguments is helpful for any function with more than a few arguments. We’d like to write:

1
2
3
4
5
6
7
8

(foo#:a10#:b"foo"#:c13#:d"bar"#:e"baz"#:f#f#:g"x"#:h42)

That way, Racket could help us catch mistakes. Even better, we’re free to supply the arguments in a different order, and it’s OK. It’s by-name, not by-position.

As a bonus, it would be great to have optional arguments, with a default value. (Especially since structs #:auto option requires all fields to share the same default value.)

Certainly we could define a foo/keyword function like this, which calls the plain foo struct constructor. I’ve done this many times. Admittedly, if you change the foo struct, you have to change this function, too. But usually they’re adjacent in the source code, and anyway it’s only the one place to make the mistake.

Even so, it would be neat if Racket had an option to create such keyword argument constructors for structs automatically.

A macro

Well, this is Racket. Any sentence that starts with, “It would be neat if Racket could ___”, can be answered with, “And I can add that to Racket myself!”

We’re defining a function whose name is the struct name with "/kw" appended. For each struct field, we want a keyword argument, where the keyword is similar to the field name. Also, we’d like to support optional arguments.

Lines 2–6 require some modules that aren’t part of the racket/base environment that macros run in.

Lines 8–9 define a helper function that can be used by a macro. To do that, the function must be define in a begin-for-syntax form.

Line 11 onward is the macro definition.

Lines 12–16 define a syntax class to use with syntax-parse. The class matches struct fields, which can be either an identifier alone or an [identifier default-value] form. In both cases, the syntax class defines an extra bit of syntax, ctor-arg. For each field, this is the arg spec to use in the definition of our special constructor function. This will be something like #:id id in the first case or #:id [id default] in the second case.

The first, ctor-id, is simply the name of our constructor function — append /kw to the user’s struct identifier.

The second, ctor-arg, is our list of arg specs for the constructor function. We’ll need to append* these — “flatten” them one level, from a list of lists into a list. That’s the reason for the funny nested ellipses: ((ctor-arg ...) ...) — it sets us up to say ctor-arg ... ... down on line 23.

Finally lines 21–24 are the template — the syntax we’re returning. This is simply a struct definition plus the definition of our special constructor function. Again, the business with the double ellipses is how we append* a list of lists like this:

1

'((#:aa)(#:bb)(#:c[c42]))

down to:

1

'(#:aa#:bb#:c[c42])

Which is the argument list we want for our constructor.

And that’s it. Although this macro doesn’t exhaustively cover all possible struct options, it’s an example of something you could use in a project to write code that is less repetitive and more resilient.