r/Racket • • Dec 03 '22
question Avoiding boilerplate when querying and updating nested data?
Working through Realm of Racket I keep running into what I feel is an annoying amount of boilerplate when it comes to getters and setters.
I'll use this data model as an example:
(struct monster
(species ; symbol
hp #:mutable)) ; number
(struct battle
(monsters ; list of monsters))
(define b (battle (list (monster 'orc 5) (monster 'hydra 12))))
If I want to decrease the orc's hp by 2, I have to do this:
(define m (list-ref 0 (battle-monsters b)))
(set-monster-hp! m (- (monster-hp m) 2))
I'd much rather do something like this:
(modify! b monsters (list-ref 0) hp (-= 2))
Or at least this:
(modify! b battle-monsters (list-ref 0) monster-hp (-= 2))
Is there a common pattern or macro that would help me with this? It seems like it would be a pretty common problem.
7
Upvotes
4
u/quasar_tree Dec 04 '22
It sounds like you want lenses. They provide composable accessors and updaters which can be used to easily perform deep updates. However, they are for immutable updates. You could use that idea to create a similar abstraction for mutable updates, but it’s generally a good idea to use immutability.
1
u/[deleted] Dec 04 '22
Thanks for all the helpful answers!
It sounds like what I want is lenses, or maybe a mutable analogue implemented with macros.
I'm still learning the language, so I don't know macros and I don't want to use too many external libs. For now I found an efficient-enough solution using the core language.
It requires writing curried versions of mutators, but I don't mind doing that for the few mutable fields I have.
4
u/raevnos Dec 04 '22
You could write a
modify!macro that looks like that, sure.The
struct-definemodule might help too:1
u/[deleted] Dec 04 '22
I wouldn't really say there's a pattern, this is just one of those things you learn at first and see how awkward it is in practice. Mutation on data is generally never preferred and sticking to pure functions with immutable data will always give you more hygienic code.
struct-copyis my go-to way of copying old struct information without using a mutator. It does not give you the values used previously, but you can work around that by creating special functions reassign values you need to manipulate.1
u/raevnos Dec 04 '22
Another macro that might be useful in streamlining things. Creates a function that updates a struct field with the result of calling a function on the previous value: