Guard Statements
| (require guard) | package: guard |
This package provides a Racket syntax for guard statements. A guard statement ensures that condition is true before executing statements after the guard. Boolean conditions can be checked with guard, and pattern matching conditions can be checked with guard-match. Guard statements can only be used within guarded blocks, which are either uses of the guarded-block macro or bodies of functions defined with define/guard.
Guard statements allow writing code linearly when it would ordinarily require deep nesting of forms like cond and match. Compare the following two function definitions, one of which uses traditional Racket forms and the other of which uses guard statements.
(define (lists-equal? xs ys) (cond [(empty? xs) (empty? ys)] [(empty? ys) #false] [else (match-define (cons x rest-xs) xs) (match-define (cons y rest-ys) ys) (and (equal? x y) (lists-equal? rest-xs rest-ys))]))
(define/guard (lists-equal? xs ys) (guard-match (cons x rest-xs) xs #:else (empty? ys)) (guard-match (cons y rest-ys) ys #:else #false) (and (equal? x y) (lists-equal? rest-xs rest-ys)))
Guard statements cooperate with macro expansion. Macros can expand into uses of guard and guard-match. Such user-defined guard statements are recognized by guarded-block and define/guard via local expansion.
syntax
(guard condition-expr #:else fail-body ...+)
condition-expr : any/c
(define/guard (add-positive x y) (guard (positive? x) #:else 'nonpositive-x) (guard (positive? y) #:else 'nonpositive-y) (+ x y))
> (add-positive -4 7) 'nonpositive-x
> (add-positive 4 -7) 'nonpositive-y
> (add-positive 4 7) 11
syntax
(guard-match match-pattern expr #:else fail-body ...+)
expr : any/c
(define/guard (zip-lists zipper xs ys) (guard-match (cons x rest-xs) xs #:else '()) (guard-match (cons y rest-ys) ys #:else '()) (cons (zipper x y) (zip-lists zipper rest-xs rest-ys)))
> (zip-lists (λ (action animal) (format "~a, ~a!" action animal)) (list "jump" "dig" "hop") (list "dog" "mole" "rabbit")) '("jump, dog!" "dig, mole!" "hop, rabbit!")
Additionally, multiple patterns may be given in place of match-pattern using values. This causes the guard to expect expr to evaluate to multiple values, one per given pattern, or else an error is raised. Each value is matched against the corresponding pattern, and if any of them do not match, the fail-body ... branch is taken. This form is similar to match-define-values.
(define/guard (zip-lists zipper xs ys) (guard-match (values (cons x rest-xs) (cons y rest-ys)) (values xs ys) #:else '()) (cons (zipper x y) (zip-lists zipper rest-xs rest-ys)))
> (zip-lists (λ (action animal) (format "~a, ~a!" action animal)) (list "jump" "dig" "hop") (list "dog" "mole" "rabbit")) '("jump, dog!" "dig, mole!" "hop, rabbit!")
syntax
(define/guard (head args) body ...+)
head = id | (head args) args = arg ... | arg ... . rest-id arg = arg-id | [arg-id default-expr] | keyword arg-id | keyword [arg-id default-expr]
(define/guard (filter-and-double-numbers xs) (guard-match (cons x rest-xs) xs #:else '()) (guard (number? x) #:else (filter-and-double-numbers rest-xs)) (cons (* x 2) (filter-and-double-numbers rest-xs)))
> (filter-and-double-numbers (list 1 2 'apple 3 'banana 4 5)) '(2 4 6 8 10)
syntax
(guarded-block body ...+)