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
(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)))

View File

@ -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)
]}