Both pattern-matching libraries in PLT Scheme allow you to match against fields of a structure, but they only do so by position, as in the following example, in which
baz is bound to the first field and
bar to the second, despite the fact that the names are reversed:
(require (lib "plt-match.ss"))
(define-struct foo (bar baz))
(match (make-foo 3 4)
[(struct foo (baz bar)) bar])
Matching by position has two disadvantages. First, it's sensitive to the order of the fields within the structure definition, and second, it requires you to include a pattern (even if it's only
_) for each field in the structure, even if you only care about one of them (as above).
Sometimes it would be convenient to match and decompose a structure by field name, rather than field order.
We can do this in
plt-match.ss, using a combination of the
(? exp pat ...) and
(app exp pat) patterns. Unfortunately, these aren't documented very thoroughly on the
plt-match.ss page.
- To match a value against a
(? exp pat ...) clause, we evaluate exp to a unary function and apply it to the value. If it returns a true value, then we match each remaining pat against the value. (The match.ss help-desk page does describe this pattern in detail.)
- To match a value against an
(app exp pat) clause, we evaluate exp to a unary function, apply it to the value, and match the result against pat.
The following function demonstrates how to use these together to match by field name:
(require (lib "plt-match.ss"))
(define-struct expr (srcloc))
(define-struct (id expr) (name))
(define-struct (lam expr) (arg body))
(define-struct (app expr) (rator rand))
(define (eval e r) (match e
[(? id? (app id-name name))
(cdr (assq name r))]
[(? app? (app app-rand rand)
(app app-rator (? lam? (app lam-arg arg)
(app lam-body body))))
(eval body (cons (cons arg rand) r))]))
This is part of an evaluator (as opposed to a partial evaluator!) for a simple expression language. The AST nodes contain a
srcloc field for source location information to use in error messages, but the evaluator doesn't care about this.
The first match clause checks to see if
e is an
id, applies the
id-name selector to it, and binds the result to
name. Note that we have to check
id? explicitly; otherwise,
id-name will signal an error if
e is an
app.
The second match clause checks to see if
e is an
app. If it is, it extracts its
rand field with
app-rand and its rator field with
app-rator. The
rator field is then matched against a nested pattern that deconstructs the
lam value, again by name rather than by position.
Neither match clause has to do anything with the
srcloc field, despite it being the first field in all three structures. Also,
the second clause matches the fields of the
app structure out of order, just to show that this is possible.
As far as I can tell, this is not possible with the
match.ss library, as it doesn't have an equivalent to the
(app exp pat) pattern form.
The syntax above is quite verbose and convoluted. It'd be wonderful if someone could write a match-expander or a macro to clean this up, but my macro-fu and match-fu are, sadly, not sufficient to the task.
Two follow-ups:
- This idiom is indeed possible with the
match.ss library, but instead of writing (app exp pat), one writes ( = exp pat). (Forgive the space in front of the equals sign; it's required by the wiki's markup language.)
- I have learned enough macro-fu and match-fu to write a library to clean this idiom up. See the
views.plt package in PLaneT: http://planet.plt-scheme.org/300/#views.plt (for PLT Scheme v350 and later only).
--
RichardCobbe - 03 Nov 2006
--
RichardCobbe - 01 Jan 2006