How to bind an identifier to a sublist plus delimiter?

77 Views Asked by At

This match binds identifiers a and b to the prefix '(0 1) and the suffix '(3 4 5) of the list:

(match '(0 1 2 3 4 5)
  [`(,a ... 2 ,b ...)
   (values a b)])

Another equivalent version:

(match '(0 1 2 3 4 5)
  [`(,@(list a ... 2) ,b ...)
   (values a b)])

How to bind an identifier (within the pattern itself) to the prefix '(0 1 2), including the delimiter?

1

There are 1 best solutions below

2
Shawn On BEST ANSWER

The app pattern, which invokes a function with the value being matched and then matches the values it returns, combined with a version of splitf-at that includes the partition element in the first list instead of the second, can be used to do this:

; Like splitf-at but includes the element to split at in the first value not the second
(define (splitf-at/inclusive lst pred?)
  (let loop ([lst lst]
             [first-res '()])
    (cond
      ((empty? lst)
       (values (reverse first-res) '()))
      ((pred? (car lst))
       (loop (cdr lst) (cons (car lst) first-res)))
      (else
       (values (reverse (cons (car lst) first-res)) (cdr lst))))))

; Gives '(0 1 2) '(3 4 5)
(match '(0 1 2 3 4 5)
  ((? list? (app (lambda (lst) (splitf-at/inclusive lst (negate (curry = 2)))) a b))
   (values a b)))

(Note the use of (? list? ...) to make sure the value is a list before trying to call any functions that depend on that.)

You can define a match extender to make it nicer-looking:

(define-match-expander split-list
  (lambda (stx)
    (syntax-case stx (...)
      ((split-list x (... ...) val y (... ...))
       #'(? (lambda (lst) (and (list? lst) (member val lst)))
            (app (lambda (lst) (splitf-at/inclusive lst (lambda (elem) (not (equal? elem val))))) x y))))))

; Also gives '(0 1 2) '(3 4 5)
(match '(0 1 2 3 4 5)
  ((split-list a ... 2 b ...)
   (values a b)))

This version also includes a check to make sure the value you want to split on is actually in the list, or it'll fail to match.