2.6 Pattern-based Updating
(require ocular-patdown/update) | package: ocular-patdown |
syntax
(update target-expr clause ...)
clause = [pat body ...+]
Like match for performing immutable updates. Also useful for creating composed optics.
Patterns act like trees of optic compositions (see optic-compose) with variables as leaves, binding composed optics. However, pattern-bound variables have special behavior when used in a clause body. Variable references retrieve the value of the focus of its optic. The variable must be "single-valued" (correspond to a lens). Using set! on a pattern-bound variable will update the current copy of the target using the optic to set the focus to a new value (see optic-set!). Note that we are only mutating a parameter tracking the current copy of the target, not the target itself. To retrieve a variable’s optic itself, use opticb.
> (struct posn [x y] #:transparent) ; get focus
> (update (posn 1 2) [(posn x) x]) 1
; set focus
> (update (posn 1 2) [(posn x) (set! x 3)]) (posn 3 2)
; no mutation of the original target > (define p (posn 1 2))
> (update p [(posn x) (set! x 3)]) (posn 3 2)
> p (posn 1 2)
; multiple independent updates
> (update (list (posn 1 2) (posn 3 4)) [(list (posn x) p) (modify! x -) (set! p (posn 5 6))]) (list (posn -1 2) (posn 5 6))
; 'multi-valued' update
> (update (list (posn 1 2) (posn 3 4) (posn 5 6)) [(list-of (posn x)) (modify! x -)]) (list (posn -1 2) (posn -3 4) (posn -5 6))
; cannot use references or set! on a 'multi-valued' variable
> (update (list 1 2 3) [(list-of x) x]) lens-get: contract violation
expected: lens?
given: #<make-traversal>
in: the 1st argument of
(-> lens? any/c any/c)
contract from:
<pkgs>/ocular-patdown/optics/lens.rkt
blaming: <pkgs>/ocular-patdown/optics/optic.rkt
(assuming the contract is correct)
at: <pkgs>/ocular-patdown/optics/lens.rkt:35:3
> (update (list 1 2 3) [(list-of x) (set! x 0)]) lens-set: contract violation
expected: lens?
given: #<make-traversal>
in: the 1st argument of
(-> lens? any/c any/c any/c)
contract from:
<pkgs>/ocular-patdown/optics/lens.rkt
blaming: <pkgs>/ocular-patdown/optics/optic.rkt
(assuming the contract is correct)
at: <pkgs>/ocular-patdown/optics/lens.rkt:38:3
; multiple clauses
> (update (list 1 2 3) [(list a b) (error "boom")] [(list a b c) (set! c 4)]) '(1 2 4)
; get and set
> (update (list 1 2) [(list a b) (set! a (+ 4 b))]) '(6 2)
2.6.1 Patterns
The grammar for patterns is as follows:
pat | = | _ | ||
| | id | |||
| | (and pat ...) | |||
| | (cons pat pat) | |||
| | (list pat ...) | |||
| | (list-of pat) | |||
| | (struct-field struct-id id pat) | |||
| | (struct-field struct-id id) | |||
| | (struct-id field-spec ...) | |||
| | (iso expr expr expr pat) | |||
| | (optic expr expr pat) |
field-spec | = | [field-id pat] | ||
| | field-id |
More details on the different patterns:
syntax
> (update 1 [a a]) 1
syntax
(and pat ...)
syntax
(cons car-pat cdr-pat)
> (update (cons 1 2) [(cons a b) (set! a 3)]) '(3 . 2)
> (update (list #t #f) [(cons a (cons b _)) (set! b 'true)]) '(#t true)
syntax
(list pat ...)
The last pattern can be ellipsized, which will give it the behavior of list-of on the tail of the list.
> (update (list 1 2 3 4) [(list-of n) (modify! n sqr)]) '(1 4 9 16)
> (update '((1 2 3) (4 5 6) () (7)) [(list-of (list-of n)) (modify! n sqr)]) '((1 4 9) (16 25 36) () (49))
> (update (list (posn 1 2) (posn 3 4)) [(list-of (posn x)) (modify! x -)]) (list (posn -1 2) (posn -3 4))
The second form is shorthand for (struct-field struct-name field-name field-name).
Cannot be used on fields from a struct’s super type.
> (update (posn 1 2) [(struct-field posn x x-value) (set! x-value 3)]) (posn 3 2)
> (update (posn 1 2) [(struct-field posn x) (set! x 3)]) (posn 3 2)
> (struct posn3 posn [z] #:transparent)
> (update (posn3 3 4 5) [(struct-field posn3 z) (set! z 9)]) (posn3 3 4 9)
> (update (posn3 3 4 5) [(struct-field posn x) (set! x 9)]) (posn 9 4)
Naively trying to use a super type’s struct field to perform an update on an instance of the subtype will yield an instance of the super type.
syntax
A wrapper around struct-field for syntactic convenience. The order of fields doesn’t matter and it is not necessary to supply all fields.
Useful for treating values of one type as values of another, equivalent type.
> (update 'foo [(iso symbol? symbol->string string->symbol str) (modify! str string-upcase)]) 'FOO
syntax
(optic target? optic-expr pat)
target? : (-> any/c boolean?)
optic-expr : optic? Matches a value that satisfies target?. Matches pat against the focus or foci of optic-expr, where optic-expr is an optic. Composes optic-expr.
This is useful for using optics as patterns and defining new patterns using optics and define-update-syntax. In fact, most of the standard patterns are defined this way.
Example:
2.6.2 Getter and Updater Utilities
parameter
(current-update-target target) → void? target : any/c
> (update (list 1 2) [(list a b) (current-update-target)]) '(1 2)
> (update (list 1 2) [(list a b) (set! a 4) (current-update-target)]) '(4 2)
(optic optic-id) Gets the optic corresponding to optic-id.
Examples:
procedure
(optic-set! op value) → any/c
op : lens? value : any/c
Using set! on a variable bound by update will use optic-set!, so you can just use set! instead.
> (update (cons 1 2) [(cons a _) (optic-set! (optic a) #t)]) '(#t . 2)
> (update (cons 1 2) [(cons a _) (set! a #t)]) '(#t . 2)
> (current-update-target (cons 1 2)) > (optic-set! car-lens -1) '(-1 . 2)
> (current-update-target) '(-1 . 2)
syntax
(modify! optic-var proc)
syntax
(fold optic-var proc init)
2.6.3 Extending Update
syntax
(define-update-syntax id transformer-expr)
transformer-expr : (-> syntax? syntax?)
Defines a pattern macro named id bound to transformer-expr. id is defined in a separate binding space, so it is safe to shadow names that are already defined by Racket. This macro is only usable in the pattern language of update.