Split shorthand into separate macro, adjust docs

This commit is contained in:
Jack Firth 2015-08-20 22:12:57 -07:00
parent 5a10edb1f3
commit 4965d54fa3
2 changed files with 66 additions and 38 deletions

View File

@ -8,27 +8,42 @@
(module+ test (module+ test
(require rackunit)) (require rackunit))
(provide struct-nested-lens) (provide struct-nested-lens
struct-nested-lens*)
(define-syntax 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 (syntax-parser
[(_ struct-id:id field-id:id) [(_ struct-id:id field-id:id)
#'(struct-lens struct-id field-id)] #'(struct-lens struct-id field-id)]
[(_ struct-id:id [field-id:id field-struct-id:id] rest ...) [(_ struct-id:id both0:id both:id ... field-id:id)
#'(lens-thrush (struct-lens struct-id field-id) #'(lens-thrush (struct-lens struct-id both0)
(struct-nested-lens field-struct-id rest ...))] (struct-nested-lens* both0 both ... field-id))]))
[(_ 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 ...)]))
(module+ test (module+ test
(struct a (b b2) #:prefab) (struct game (player level) #:transparent)
(struct b (b1 b2 b3) #:prefab) (struct player (posn stats) #:transparent)
(define a-b-b1-lens (struct-nested-lens a b b1)) (struct posn (x y) #:transparent)
(define a-b2-b3-lens (struct-nested-lens a [b2 b] b3)) (struct combat-stats (health attack) #:transparent)
(check-equal? (lens-view a-b-b1-lens (a (b 1 2 3) 'foo)) 1) (define the-game (game (player (posn 0 0) (combat-stats 10 1)) 'foo-level))
(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) (define game-player-health-lens
(check-equal? (lens-set a-b2-b3-lens (a 'foo (b 1 2 3)) 10) (a 'foo (b 1 2 10)))) (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)))

View File

@ -9,14 +9,10 @@
@(define-persistant-lenses-unstable-examples struct-nested-examples) @(define-persistant-lenses-unstable-examples struct-nested-examples)
@defform[#:id struct-nested-lens @defform[#:id struct-nested-lens
(struct-nested-lens struct-id intermediate ... field-id) (struct-nested-lens [struct-id field-id] ...)]{
#:grammar ([intermediate (code:line [subfield-id subfield-struct-id]) both-id])]{ Constructs a lens that views nested structures. Each @racket[struct-id] and
Constructs a lens that views nested structures. The first @racket[struct-id] is the @racket[field-id] pair is paired into a lens for viewing that field of that
outermost struct type, the last @racket[field-id] is the innermost target field. Each struct, then the list of lenses are @racket[lens-thrush]ed together.
@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.
For example, given a complicated nested tree of state representing a game: For example, given a complicated nested tree of state representing a game:
@struct-nested-examples[ @struct-nested-examples[
@ -29,22 +25,39 @@
] ]
We can create a lens for traversing the nested structures of the game state. 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 At each step, we provide the name of the struct we're examining and the name
as the struct that that field is a value of. of the field we wish to traverse into.
@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.
@struct-nested-examples[ @struct-nested-examples[
(define game-player-health-lens (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-view game-player-health-lens the-game)
(lens-set game-player-health-lens the-game 20) (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)
]}