2.3 Traversals
(require ocular-patdown/optics/traversal) | |
package: ocular-patdown |
A traversal is an optic that can have zero or more foci. Updating with a traversal is like using map.
> (traversal-map list-traversal (list 1 2 3) add1) '(2 3 4)
> (traversal-foldl list-traversal (list 1 2 3) cons '()) '(3 2 1)
All lenses are traversals, but not all traversals are lenses.
procedure
(traversal? val) → boolean?
val : any/c
> (traversal? list-traversal) #t
> (traversal? car-lens) #t
procedure
(make-traversal map-proc fold-proc) → traversal?
map-proc : (-> (-> any/c any/c) any/c any/c) fold-proc : (-> (-> any/c any/c any/c) any/c any/c any/c)
traversal-maping a target under a traversal should leave it with the same number of foci.
traversal-foldl should not call the folding procedure with the same focus twice.
2.3.1 Traversal Operations
procedure
(traversal-map traversal target proc) → any/c
traversal : traversal? target : any/c proc : (-> any/c any/c)
> (traversal-map list-traversal (list 1 2 3) add1) '(2 3 4)
> (traversal-map vector-traversal (vector 1 2 3) sub1) '#(0 1 2)
procedure
(traversal-foldl traversal target proc init) → any/c
traversal : traversal? target : any/c proc : (-> any/c any/c any/c) init : any/c
> (traversal-foldl list-traversal (list 1 2 3) + 0) 6
> (traversal-foldl vector-traversal (vector 1 2 3) cons '()) '(3 2 1)
procedure
(traversal->list traversal target) → (listof any/c)
traversal : traversal? target : any/c
> (traversal->list vector-traversal (vector 1 2 3 4)) '(1 2 3 4)
procedure
(traversal-compose traversal ...) → traversal?
traversal : traversal?
> (define lol-traversal (traversal-compose list-traversal list-traversal)) > (traversal-map lol-traversal '((1 2 3) () (4)) add1) '((2 3 4) () (5))
> (traversal-map (traversal-compose list-traversal maybe-traversal car-lens) '(#f (10 20 30) #f (40 50)) sqr) '(#f (100 20 30) #f (1600 50))
2.3.2 Library Traversals
value
> (traversal-map list-traversal (list 1 2 3) add1) '(2 3 4)
> (traversal-foldl list-traversal (list 1 2 3) cons '()) '(3 2 1)
value
> (traversal-map vector-traversal (vector 1 2 3) add1) '#(2 3 4)
> (traversal-foldl vector-traversal (vector 1 2 3) cons '()) '(3 2 1)
value
Raises an exception when mapping results in a focus turning into a list since this could lead to the number of foci changing.
> (traversal-map rose-traversal '((1 2) () ((3) 4)) add1) '((2 3) () ((4) 5))
> (traversal-foldl rose-traversal '(("a" "b") "c") string-append "") "cba"
value
> (traversal-map maybe-traversal 1 add1) 2
> (traversal-map maybe-traversal #f add1) #f
> (traversal->list maybe-traversal 1) '(1)
> (traversal->list maybe-traversal #f) '()
> (traversal-map (traversal-compose list-traversal maybe-traversal car-lens) '(#f (10 20 30) #f (40 50)) sqr) '(#f (100 20 30) #f (1600 50))
2.3.3 Traversal Combinators
procedure
(traversal-append trv ...) → traversal?
trv : traversal?
> (traversal-map (traversal-append car-lens cdr-lens) (cons 1 2) add1) '(2 . 3)
Note: In order for this to be a lawful traversal, the foci of the input traversals must never have overlap.
syntax
(if-traversal pred then-traversal else-traversal)
Wraps then-traversal and else-traversal with lazy-traversal.
> (struct bt [left right] #:transparent) > (struct leaf [value] #:transparent)
> (define bt-values-traversal (if-traversal bt? (traversal-compose (traversal-append (struct-lens bt left) (struct-lens bt right)) bt-values-traversal) (struct-lens leaf value)))
> (define bt1 (bt (leaf 1) (bt (bt (leaf 2) (bt (leaf 3) (leaf 4))) (leaf 5)))) > (traversal->list bt-values-traversal bt1) '(1 2 3 4 5)
> (traversal-map bt-values-traversal bt1 add1) (bt (leaf 2) (bt (bt (leaf 3) (bt (leaf 4) (leaf 5))) (leaf 6)))
In order for such a traversal to be lawful, it is a good idea to make sure it’s impossible for traversal-map to change the result on the condition of the target. In other words, if a target will end up using then-traversal, mapping over its foci better not cause the resulting target to use the else-traversal or vice versa. That’s why rose-traversal has the restriction that the mapped procedure cannot return a list.
syntax
(lazy-traversal body ...)
> (struct tree [value children] #:transparent)
> (define tree1 (tree 1 (list (tree 2 (list (tree 3 '()))) (tree 4 '())))) > (define tree-value-lens (struct-lens tree value)) > (define tree-children-lens (struct-lens tree children)) > (define tree-children-traversal (traversal-compose tree-children-lens list-traversal))
> (define tree-values-traversal (lazy-traversal (traversal-append tree-value-lens (traversal-compose tree-children-traversal tree-values-traversal)))) > (traversal-foldl tree-values-traversal tree1 + 0) 10
2.3.4 Generic Traversal Interface
syntax
> (struct make-traversal (map foldl) #:methods gen:traversal [(define (traversal-map t target proc) ((make-traversal-map t) proc target)) (define (traversal-foldl t target proc init) ((make-traversal-foldl t) proc init target))])