diff --git a/unstable/lens/struct-nested.rkt b/unstable/lens/struct-nested.rkt index ad78dec..ffd5f78 100644 --- a/unstable/lens/struct-nested.rkt +++ b/unstable/lens/struct-nested.rkt @@ -8,27 +8,42 @@ (module+ test (require rackunit)) -(provide struct-nested-lens) +(provide struct-nested-lens + struct-nested-lens*) (define-syntax struct-nested-lens + (syntax-parser + [(_ [struct-id:id field-id:id] ...) + #'(lens-thrush (struct-lens struct-id field-id) ...)])) + +(define-syntax struct-nested-lens* (syntax-parser [(_ struct-id:id field-id:id) #'(struct-lens struct-id field-id)] - [(_ struct-id:id [field-id:id field-struct-id:id] rest ...) - #'(lens-thrush (struct-lens struct-id field-id) - (struct-nested-lens field-struct-id rest ...))] - [(_ struct-id:id field-and-field-struct-id:id rest ...) - #'(struct-nested-lens struct-id - [field-and-field-struct-id field-and-field-struct-id] - rest ...)])) + [(_ struct-id:id both0:id both:id ... field-id:id) + #'(lens-thrush (struct-lens struct-id both0) + (struct-nested-lens* both0 both ... field-id))])) (module+ test - (struct a (b b2) #:prefab) - (struct b (b1 b2 b3) #:prefab) - (define a-b-b1-lens (struct-nested-lens a b b1)) - (define a-b2-b3-lens (struct-nested-lens a [b2 b] b3)) - (check-equal? (lens-view a-b-b1-lens (a (b 1 2 3) 'foo)) 1) - (check-equal? (lens-set a-b-b1-lens (a (b 1 2 3) 'foo) 10) (a (b 10 2 3) 'foo)) - (check-equal? (lens-view a-b2-b3-lens (a 'foo (b 1 2 3))) 3) - (check-equal? (lens-set a-b2-b3-lens (a 'foo (b 1 2 3)) 10) (a 'foo (b 1 2 10)))) + (struct game (player level) #:transparent) + (struct player (posn stats) #:transparent) + (struct posn (x y) #:transparent) + (struct combat-stats (health attack) #:transparent) + (define the-game (game (player (posn 0 0) (combat-stats 10 1)) 'foo-level)) + + (define game-player-health-lens + (struct-nested-lens [game player] + [player stats] + [combat-stats health])) + (check-equal? (lens-view game-player-health-lens the-game) 10) + (check-equal? (lens-set game-player-health-lens the-game 20) + (game (player (posn 0 0) (combat-stats 20 1)) 'foo-level)) + + (define game-player-posn-x-lens + (struct-nested-lens* game player posn x)) + (check-equal? (lens-view game-player-posn-x-lens the-game) 0) + (check-equal? (lens-set game-player-posn-x-lens the-game 3) + (game (player (posn 3 0) (combat-stats 10 1)) 'foo-level))) + + \ No newline at end of file diff --git a/unstable/lens/struct-nested.scrbl b/unstable/lens/struct-nested.scrbl index 51bdc7a..6110e58 100644 --- a/unstable/lens/struct-nested.scrbl +++ b/unstable/lens/struct-nested.scrbl @@ -9,14 +9,10 @@ @(define-persistant-lenses-unstable-examples struct-nested-examples) @defform[#:id struct-nested-lens - (struct-nested-lens struct-id intermediate ... field-id) - #:grammar ([intermediate (code:line [subfield-id subfield-struct-id]) both-id])]{ - Constructs a lens that views nested structures. The first @racket[struct-id] is the - outermost struct type, the last @racket[field-id] is the innermost target field. Each - @racket[intermediate] specifies how to walk down one level of the nested structs, and - is either a pair of a @racket[subfield-id] and that field's struct type in - @racket[subfield-struct-id], or a single @racket[both-id] in the common case that the - field name is the same as the struct name. + (struct-nested-lens [struct-id field-id] ...)]{ + Constructs a lens that views nested structures. Each @racket[struct-id] and + @racket[field-id] pair is paired into a lens for viewing that field of that + struct, then the list of lenses are @racket[lens-thrush]ed together. For example, given a complicated nested tree of state representing a game: @struct-nested-examples[ @@ -29,22 +25,39 @@ ] We can create a lens for traversing the nested structures of the game state. - This takes advantage of the fact that each struct's fields are the same name - as the struct that that field is a value of. - @struct-nested-examples[ - (define game-player-posn-x-lens - (struct-nested-lens game player posn x)) - (lens-view game-player-posn-x-lens the-game) - (lens-set game-player-posn-x-lens the-game 3) - ] - - In the case of the player's combat stats, the field is @italic{not} the - same name as the @racket[combat-stats] struct. Therefore, we use the - more verbose @racket[[subfield-id subfield-struct-id]] form for that - step of the nested struct traversal. + At each step, we provide the name of the struct we're examining and the name + of the field we wish to traverse into. @struct-nested-examples[ (define game-player-health-lens - (struct-nested-lens game player [stats combat-stats] health)) + (struct-nested-lens [game player] + [player stats] + [combat-stats health])) (lens-view game-player-health-lens the-game) (lens-set game-player-health-lens the-game 20) ]} + +@(define-persistant-lenses-unstable-examples struct-nested*-examples) + +@defform[#:id struct-nested-lens* + (struct-nested-lens* struct-id both-id ... field-id)]{ + Like @racket[struct-nested-lens], but for the case where each nested + field is named the same as it's struct type. For example, given the + game state defined in the examples for @racket[struct-nested-lens]: + @struct-nested*-examples[ + (struct game (player level) #:transparent) + (struct player (posn stats) #:transparent) + (struct posn (x y) #:transparent) + (struct combat-stats (health attack) #:transparent) + (define the-game (game (player (posn 0 0) (combat-stats 10 1)) 'foo-level)) + the-game + ] + + Because each field is named the same as it's struct type, we can + create a lens for viewing the player's x coordinate more succinctly + than with @racket[struct-nested-examples]: + @struct-nested*-examples[ + (define game-player-x-lens + (struct-nested-lens* game player posn x)) + (lens-view game-player-x-lens the-game) + (lens-set game-player-x-lens the-game 5) +]}