17

Suppose I have a struct with many fields:

(struct my-struct (f1 f2 f3 f4)) 

If I am to return a new struct with f2 updated, I have to rephrase every other fields:

(define s (my-struct 1 2 3 4)) (my-struct (my-struct-f1 s) (do-something-on (my-struct-f2 s)) (my-struct-f3 s) (my-struct-f4 s)) 

Which is redundant and would be a source of bugs if I update the number of the fields or changed their orders.

I really wonder if there's a such way I can update a specific field for a struct like:

(my-struct-f2-update (my-struct 1 2 3 4) (lambda (f2) (* f2 2))) ;; => (my-struct 1 4 3 4) 

Or I can just set them to a new value as:

(define s (my-struct 1 2 3 4) (my-struct-f2-set s (* (my-struct-f2 s) 2)) ;; => (my-struct 1 4 3 4) 

Notice, this is not mutating s here; my-struct-f2-update and my-struct-f2-set should be just returning a copy of s with f2 field updated.

In Haskell I know the 'lens' library that does this job. I'm just wondering if there are some similar ways that I can adopt for racket. Thanks.

3 Answers 3

17

You know what? This is a really good idea. In fact, there have been a few cases in which I wanted this functionality, but I didn't have it. The bad news is that nothing of this sort is provided by Racket. The good news is that Racket has macros!

I present to you define-struct-updaters!

(require (for-syntax racket/list racket/struct-info racket/syntax syntax/parse)) (define-syntax (define-struct-updaters stx) (syntax-parse stx [(_ name:id) ; this gets compile-time information about the struct (define struct-info (extract-struct-info (syntax-local-value #'name))) ; we can use it to get the constructor, predicate, and accessor functions (define/with-syntax make-name (second struct-info)) (define/with-syntax name? (third struct-info)) (define accessors (reverse (fourth struct-info))) (define/with-syntax (name-field ...) accessors) ; we need to generate setter and updater identifiers from the accessors ; we also need to figure out where to actually put the new value in the argument list (define/with-syntax ([name-field-set name-field-update (name-field-pre ...) (name-field-post ...)] ...) (for/list ([accessor (in-list accessors)] [index (in-naturals)]) (define setter (format-id stx "~a-set" accessor #:source stx)) (define updater (format-id stx "~a-update" accessor #:source stx)) (define-values (pre current+post) (split-at accessors index)) (list setter updater pre (rest current+post)))) ; now we just need to generate the actual function code #'(begin (define/contract (name-field-set instance value) (-> name? any/c name?) (make-name (name-field-pre instance) ... value (name-field-post instance) ...)) ... (define/contract (name-field-update instance updater) (-> name? (-> any/c any/c) name?) (make-name (name-field-pre instance) ... (updater (name-field instance)) (name-field-post instance) ...)) ...)])) 

If you're not familiar with macros, it can look a little intimidating, but it's actually not a complicated macro. Fortunately, you don't need to understand how it works to use it. Here's how you'd do that:

(struct point (x y) #:transparent) (define-struct-updaters point) 

Now you can use all the relevant functional setters and updaters as you'd please.

> (point-x-set (point 1 2) 5) (point 5 2) > (point-y-update (point 1 2) add1) (point 1 3) 

I believe there have been some theoretical plans to redesign the Racket struct system, and I think this would be a valuable addition. Until then, feel free to use this solution. I’ve made the code in this answer available as the struct-update package, which can be installed using raco pkg install struct-update.

Sign up to request clarification or add additional context in comments.

2 Comments

Note that this macro, like struct-copy, will lose substructure fields.
@SamTobin-Hochstadt True. It would be nice to have a way to get information about a structure instance at runtime, though I guess that might violate the concept of opaque structures.
14

I like Alexis' macro! It has more of the "lens" flavor you wanted.

I also want to point out struct-copy. Given:

#lang racket (struct my-struct (f1 f2 f3 f4) #:transparent) (define s (my-struct 1 2 3 4)) 

You can use struct-copy to set a value:

(struct-copy my-struct s [f2 200]) ;;=> (my-struct 1 200 3 4) 

Or to update a value:

(struct-copy my-struct s [f2 (* 100 (my-struct-f2 s))]) ;;=> (my-struct 1 200 3 4) 

Update: Thinking about this more, here are a few more ideas.

You could also update using match's struct* pattern:

(match s [(struct* my-struct ([f2 f2])) (struct-copy my-struct s [f2 (* 100 f2)])]) 

Of course, that's very verbose. On the other hand the struct* pattern makes it easy to define a macro using the simpler define-syntax-rule:

;; Given a structure type and an instance of it, a field-id, and a ;; function, return a new structure instance where the field is the ;; value of applying the function to the original value. (define-syntax-rule (struct-update struct-type st field-id fn) (match st [(struct* struct-type ([field-id v])) (struct-copy struct-type st [field-id (fn v)])])) (struct-update my-struct s f2 (curry * 100)) ;;=> (my-struct 1 200 3 4) 

Of course, setting is the special case where you give update a const function:

(struct-update my-struct s f2 (const 42)) ;;=> (my-struct 1 42 3 4) 

Finally, this is like struct-update, but returns an updater function, in the spirit of Alexis' macro:

(define-syntax-rule (struct-updater struct-type field-id) (λ (st fn) (struct-update struct-type st field-id fn))) (define update-f2 (struct-updater my-struct f2)) (update-f2 s (curry * 100)) ;;=> (my-struct 1 200 3 4) 

I'm not saying that any of this is idiomatic or efficient. But it's possible. :)

Comments

10

Alexis's macro is fantastic, and Greg's rightly pointed out struct-copy and match+struct*, but since you specifically mentioned lenses in your example I'll point out that there is now a lens package for Racket (disclaimer: I wrote a lot of it). It provides struct/lens and define-struct-lenses macros for your use case:

> (struct/lens foo (a b c)) > (lens-view foo-a-lens (foo 1 2 3)) 1 > (lens-set foo-a-lens (foo 1 2 3) 'a) (foo 'a 2 3) > (lens-transform foo-a-lens (foo 1 2 3) number->string) (foo "1" 2 3) 

define-struct-lenses lets you define the lenses seperately from the structs:

> (struct foo (a b c)) > (define-struct-lenses foo) 

The above is equivalent to (struct/lens foo (a b c)). If you're only operating on structs in isolation from other kinds of structs, using define-struct-updaters is simpler. But if you have a lot of nested data structures of various flavors, the ability to compose lenses makes them a powerful tool for the job.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.