Is it possible to match a set?

I can't believe I've never noticed this before, but is there a way to use match with set?

(match (set 1 2 3)
  [(set 1 2 3) 'matched]
  [_ 'not-matched])

;; Error:  syntax error in pattern in: (set 1 2 3)

I suppose it could always be done via #:when but that feels like cheating, and it doesn't allow for decomposing a set that contains non-primitive values.

(struct cell (value candidates) #:transparent)

(match (cell #f (set 1 2 4 8 9))
  [(cell _ cands) #:when (set-member? cands 4)
                  "cell can have a value of 4"]
  [else "nope"])

;; "cell can have a value of 4"

For context, I'm working on a sudoku-making program and I was going to use a system where cell is a struct that contains, among other things, the remaining candidates for that cell. This would allow me to use match to determine if the cell has a specific candidate.

Making a match expander that will pattern match on all of the elements of the set is not too bad. A problem I have with this solution is that it copies the set into a list to make a match.

(define-match-expander set^
  (syntax-parser
    [(_ pat ...)
     #'(? set? (app set->list (list-no-order pat ...)))]))

(match (set 3 2 1)
  [(set^ 1 2 3) 'ok]
  [_ 'nope])
;; => 'ok

(match (set 'y 'x 'z)
  [(set^ a b c) (list a b c)]
  [_ #f])
;; => '(x y z)

The previous expander could be modified slightly to match a subset. A better (IMO) expander that matches a subset of values (not patterns) can also be done like this:

(define-match-expander subset^-aux
  (syntax-rules ()
    [(_ v) (? (λ (s) (set-member? s v)))]
    [(_ v . vs) (? (λ (s) (set-member? s v))
                   (subset^-aux . vs))]))

(define-match-expander subset^
  (syntax-rules ()
    [(_ v ...) (? set? (subset^-aux v ...))]))

(match (for/set ([i 100]) i)
  [(subset^ 100) 'nope]
  [(subset^ 45 80 90) 'ok]
  [_ 'wha])
;; => 'ok
2 Likes

Very cool. Thank you.