패턴 매칭

패턴 매칭 (Pattern Matching)

match 형식과 관련 형식들은 Racket 값에 대한 일반적인 패턴 매칭을 지원해요. 문자열·바이트·스트림에 대한 정규식 매칭 정보는 Regular Expressions를 참고하세요.

출처: Racket Reference - Pattern Matching

본문

match 형식과 관련 형식들은 Racket 값에 대한 일반적인 패턴 매칭을 지원합니다. 문자열·바이트·스트림에 대한 정규식 매칭 정보는 Regular Expressions를 참고하세요.

( require racket/match ) — 패키지: base

이 절에서 문서화한 바인딩은 racket/matchracket 라이브러리가 제공하지만, racket/base는 제공하지 않아요.

문법

(match val-expr clause ...)

clause = [pat option=> option ... body ...+]

option=> = | (=> id)

option = #:when cond-expr | #:do [do-body ...]

val-expr의 결과와 일치하는 첫 번째 pat를 찾고, pat이 도입한 바인딩(있으면)으로 대응하는 body들을 평가해요. pat이 도입한 바인딩은 pat의 다른 부분에서는 사용할 수 없습니다. 일치하는 절의 마지막 bodymatch 표현식에 대해 꼬리 위치에서 평가됩니다.

일치를 찾으려면 clause들을 순서대로 시도합니다. 어떤 clause도 일치하지 않으면 exn:misc:match? 예외가 발생해요.

선택적 #:when cond-exprcond-expr이 참 값을 만들 때만 패턴이 일치해야 한다고 명시합니다. cond-exprpat에 바인딩된 모든 변수의 스코프 안에 있어요. cond-expr은 실패 프로시저를 호출하기 전에 매칭 중인 객체를 변이하면 안 됩니다. 그렇지 않으면 매칭 동작이 예측 불가능해집니다. 같은 목적을 이룬 더 낮은 수준의 메커니즘인 failure-cont도 함께 보세요.

예시:

(define (m x)
  (match x
    [(list a b c)
     #:when (= 6 (+ a b c))
     'sum-is-six]
    [(list a b c) 'sum-is-not-six]))
> (m '(1 2 3))
'sum-is-six
> (m '(2 3 4))
'sum-is-not-six

선택적 #:do [do-body ...]do-body 형식들을 실행해요. 특히 그 형식들은 남은 옵션과 절 본문에 보이는 정의를 도입할 수 있습니다. #:when#:do 옵션은 여러 번 나타날 수 있어요.

예시:

(define (m x)
  (match x
    [(list a b c)
     #:do [(define sum (+ a b c))]
     #:when (> sum 6)
     (format "the sum, which is ~a, is greater than 6" sum)]
    [(list a b c) 'sum-is-not-greater-than-six]))
> (m '(1 2 3))
'sum-is-not-greater-than-six
> (m '(2 3 4))
"the sum, which is 9, is greater than 6"

선택적 (=> id)pat 바로 뒤에 나타나야 하며, 인자가 0개인 *실패 프로시저(failure procedure)*에 바인딩됩니다. id는 모든 절 옵션과 절 본문에 보여요. 이 프로시저를 호출하면 패턴 매칭 표현식으로 다시 이스케이프하고, 패턴이 일치에 실패한 것처럼 매칭 과정을 재개합니다. body들은 실패 프로시저를 호출하기 전에 매칭 중인 객체를 변이하면 안 됩니다. 그렇지 않으면 매칭 동작이 예측 불가능해집니다.

예시:

(define (m x)
  (match x
    [(list a b c)
     (=> exit)
     (f x exit)]
    [(list a b c) 'sum-is-not-six]))

(define (f x exit)
  (if (= 6 (apply + x))
      'sum-is-six
      (exit)))
> (m '(1 2 3))
'sum-is-six
> (m '(2 3 4))
'sum-is-not-six

pat의 문법은 다음과 같습니다. 이탤릭체가 아닌 식별자는 상징적으로(즉 바인딩이 아니라) 인식됩니다.

pat  ::= id                                    match anything, bind identifier
       | (var id)                              match anything, bind identifier
       | _                                     match anything
       | literal                               match literal
       | (quote datum)                         match equal? value
       | (list lvp ...)                        match sequence of lvps
       | (list-rest lvp ... pat)               match lvps consed onto a pat
       | (list* lvp ... pat)                   match lvps consed onto a pat
       | (list-no-order pat ...)               match pats in any order
       | (list-no-order pat ... lvp)           match pats in any order
       | (vector lvp ...)                      match vector of pats
       | (hash expr pat ... ... ht-opt)        match hash table
       | (hash* [expr pat kv-opt] ... ht-opt)  match hash table
       | (hash-table (pat pat) ...)            match hash table - deprecated
       | (hash-table (pat pat) ...+ ooo)       match hash table - deprecated
       | (cons pat pat)                        match pair of pats
       | (mcons pat pat)                       match mutable pair of pats
       | (box pat)                             match boxed pat
       | (struct-id pat ...)                   match struct-id instance
       | (struct struct-id (pat ...))          match struct-id instance
       | (regexp rx-expr)                      match string
       | (regexp rx-expr pat)                  match string, result with pat
       | (pregexp px-expr)                     match string
       | (pregexp px-expr pat)                 match string, result with pat
       | (and pat ...)                         match when all pats match
       | (or pat ...)                          match when any pat match
       | (not pat ...)                         match when no pat matches
       | (app expr pats ...)                   match (expr value) output values to pats
       | (? expr pat ...)                      match if (expr value) and pats
       | (quasiquote qp)                       match a quasipattern
       | derived-pattern                       match using extension

literal ::= #t          match true
          | #f          match false
          | string      match equal? string
          | bytes       match equal? byte string
          | number      match equal? number
          | char        match equal? character
          | keyword     match equal? keyword
          | regexp      match equal? regexp literal
          | pregexp     match equal? pregexp literal

lvp     ::= pat ooo     greedily match pat instances
          | pat         match pat

qp      ::= literal                    match literal
          | id                         match symbol
          | (qp ...)                   match sequences of qps
          | (qp ... . qp)              match qps ending qp
          | (qp ooo . qp)              match qps beginning with repeated qp
          | #(qp ...)                  match vector of qps
          | #&qp                       match boxed qp
          | #s(prefab-key qp ...)      match prefab struct with qp fields
          | ,pat                       match pat
          | ,@(list lvp ...)           match lvps, spliced
          | ,@(list-rest lvp ... pat)  match lvps plus pat, spliced
          | ,@'qp                      match list-matching qp, spliced

ooo     ::= ...      zero or more; ... is literal
          | ___      zero or more
          | ..k      k or more
          | __k      k or more

kv-opt  ::= (none)            key must exist
          | #:default def-expr   key may not exist; match def-expr with the value pattern

ht-opt  ::= (none)        default mode
          | #:closed      closed to extension mode
          | #:open        open to extension mode
          | #:rest pat    residue mode

더 자세히, 패턴들은 다음과 같이 일치합니다.

  • id(_, ..., ___, 그리고 음이 아닌 정수 k에 대한 ..k__k라는 예약 이름은 제외) 또는 (var id) — 무엇이든 일치하고, 일치하는 값을 id에 바인딩합니다. condcase에서와 달리 match에서 else는 키워드가 아니에요. "else" 절에는 _ 패턴을 쓰세요. id가 패턴 안에서 여러 번 사용되면, 대응하는 일치가 (match-equality-test)에 따라 같아야 합니다. 단 ornot 하위 패턴이 다른 것들의 id 인스턴스는 독립적이에요. id에 대한 바인딩은 같은 패턴의 다른 부분에서는 사용할 수 없습니다. id가 서로 다른 생략점(ellipsis) 깊이에서 여러 번 사용되면(즉 어떤 사용은 ... 아래이고 다른 사용은 그 상위이거나, 서로 다른 ... 패턴 아래인 경우) 문법 오류가 발생해요. 자세한 내용과 예시는 Non-linear Patterns and Ellipses 참조.

    예시:

    > (match '(1 2 3)
        [(list a b a) (list a b)]
        [(list a b c) (list c b a)])
    '(3 2 1)
    > (match '(1 (x y z) 1)
        [(list a b a) (list a b)]
        [(list a b c) (list c b a)])
    '(1 (x y z))
    > (match #f
        [else
         (cond
           [#f 'not-evaluated]
           [else 'also-not-evaluated])])
    
  • _ — 무엇이든 일치하고, 어떤 식별자도 바인딩하지 않아요.

    예시:

    > (match '(1 2 3)
        [(list _ _ a) a])
    3
    
  • #t, #f, string, bytes, number, char, 또는 (quote datum)equal? 상수와 일치합니다.

    예시:

    > (match "yes"
        ["no" #f]
        ["yes" #t])
    #t
    
  • (list lvp ...) — 요소들의 리스트와 일치해요. (list pat ...)의 경우 패턴은 pat 수만큼의 요소를 가진 리스트와 일치하고, 각 요소는 대응하는 pat와 일치해야 합니다. 더 일반적인 경우 각 lvp는 "접합된(spliced)" 탐욕 매치 리스트에 대응합니다. 접합된 리스트에서 ...___는 0개 이상 일치의 별명이에요. ..k__k 형식도 별명인데, k개 이상 일치를 명시합니다. 이 접합 연산자 앞에 오는 패턴 변수들은 일치하는 형식들의 리스트에 바인딩됩니다.

    예시:

    > (match '(1 2 3)
        [(list a b c) (list c b a)])
    '(3 2 1)
    > (match '(1 2 3)
        [(list 1 a ...) a])
    '(2 3)
    > (match '(1 2 3)
        [(list 1 a ..3) a]
        [_ 'else])
    'else
    > (match '(1 2 3 4)
        [(list 1 a ..3) a]
        [_ 'else])
    '(2 3 4)
    > (match '(1 2 3 4 5)
        [(list 1 a ..3 5) a]
        [_ 'else])
    '(2 3 4)
    > (match '(1 (2) (2) (2) 5)
        [(list 1 (list a) ..3 5) a]
        [_ 'else])
    '(2 2 2)
    
  • (list-rest lvp ... pat) 또는 (list* lvp ... pat)list 패턴과 비슷하지만, 마지막 pat가 마지막 lvp 뒤의 리스트 "나머지"와 일치합니다. 사실 pat가 리스트가 아닌 값과 일치하면, 일치하는 값은 페어의 비리스트 체인("improper list")일 수 있어요.

    예시:

    > (match '(1 2 3 . 4)
        [(list-rest a b c d) d])
    4
    > (match '(1 2 3 . 4)
        [(list-rest a ... d) (list a d)])
    '((1 2 3) 4)
    
  • (list-no-order pat ...)list 패턴과 비슷하지만, 각 pat와 일치할 요소가 리스트에 어떤 순서로든 나타날 수 있어요.

    > (match '(1 2 3)
        [(list-no-order 3 2 x) x])
    1
    

    다른 패턴과 달리 list-no-order는 하위 패턴 사이에 중복 식별자를 허용하지 않아요. 예를 들어 (list-no-order x 1 x)(list-no-order x 1 x ...)는 둘 다 문법 오류를 만들어 냅니다.

  • (list-no-order pat ... lvp)list-no-order를 일반화해서, 다른 패턴들의 일치와 어떤 순서로든 섞여 있는 여러 리스트 요소를 일치시키는 패턴을 허용합니다.

    > (match '(1 2 3 4 5 6)
        [(list-no-order 6 2 y ...) y])
    '(1 3 4 5)
    
  • (vector lvp ...)list 패턴과 같지만, 벡터를 일치시켜요.

    > (match #(1 (2) (2) (2) 5)
        [(vector 1 (list a) ..3 5) a])
    '(2 2 2)
    
  • (hash expr pat ... ... ht-opt) — 해시 테이블을 상대로 일치하는데, expr이 키와 일치하고 pat이 대응하는 값과 일치해요.

    예시:

    > (match (hash "aa" 1 "b" 2)
        [(hash "b" b (string-append "a" "a") a)
         (list b a)])
    '(2 1)
    > (match (hash "aa" 1 "b" 2)
        [(hash "b" _ "c" _) 'matched]
        [_ 'not-matched])
    'not-matched
    

    키 일치는 일치하는 해시 테이블의 키 비교자(comparator)를 사용합니다.

    예시:

    > (let ([k (string-append "a" "b")])
        (match (hasheq "ab" 1)
          [(hash k v) 'matched]
          [_ 'not-matched]))
    'not-matched
    > (let ([k (string-append "a" "b")])
        (match (hasheq k 1)
          [(hash k v) 'matched]
          [_ 'not-matched]))
    'matched
    

    해시 테이블 값의 잔여 키-값 항목의 동작은 ht-opt에 달려 있습니다.

    ht-opt가 제공되지 않거나 #:closed이면, 해시 테이블 값의 모든 키가 일치되어야 해요. 즉 일치가 확장에 닫혀 있습니다.

    > (match (hash "a" 1 "b" 2)
        [(hash "b" _) 'matched]
        [_ 'not-matched])
    'not-matched
    

    ht-opt#:open이면, 해시 테이블 값에 패턴에 명시되지 않은 키가 있을 수 있어요. 즉 일치가 확장에 열려 있습니다.

    > (match (hash "a" 1 "b" 2)
        [(hash "b" _ #:open) 'matched]
        [_ 'not-matched])
    'matched
    

    ht-opt#:rest pat이면, pat이 잔여 해시 테이블을 상대로 추가로 일치합니다. 일치하는 해시 테이블이 불변하면 이 잔여 일치는 효율적이에요. 그렇지 않으면 일치하는 해시 테이블이 복사되는데, 비쌀 수 있습니다.

    > (match (hash "a" 1 "b" 2)
        [(hash "b" _ #:rest (hash "a" a)) a]
        [_ #f])
    1
    

    많은 키 expr이 같은 값으로 평가될 수 있어요.

    > (match (hash "a" 1 "b" 2)
        [(hash "b" _ "b" 2 "a" _) 'matched]
        [_ 'not-matched])
    'matched
    
  • (hash* [expr pat kv-opt] ... ht-opt)hash와 비슷하지만 다음과 같은 차이가 있어요. 키-값 패턴이 문법적으로 그룹화되어야 합니다. ht-opt가 명시되지 않으면 #:open처럼 동작합니다(#:closed와 반대로). kv-opt#:default def-expr로 명시되고 키가 해시 테이블 값에 없으면, 즉시 일치 실패하는 대신 def-expr의 기본값이 값 패턴을 상대로 일치됩니다.

    예시:

    > (match (hash "a" 1 "b" 2)
        [(hash* ["b" b] ["a" a]) (list b a)])
    '(2 1)
    > (match (hash "a" 1 "b" 2)
        [(hash* ["b" b]) 'matched]
        [_ 'not-matched])
    'matched
    > (match (hash "a" 1 "b" 2)
        [(hash* ["a" a #:default 42] ["c" c #:default 100]) (list a c)]
        [_ #f])
    '(1 100)
    
  • (hash-table (pat pat) ...) — 이 패턴은 부정확할 수 있어서 더 이상 권장되지 않습니다(deprecated). 하지만 많은 프로그램이 그 부정확한 동작에 의존하므로, 이전 버전 호환성을 위해 여전히 제공합니다. list-no-order와 비슷하지만 해시 테이블의 키-값 쌍을 상대로 일치해요.

    > (match #hash(("a" . 1) ("b" . 2))
        [(hash-table ("b" b) ("a" a)) (list b a)])
    '(2 1)
    
  • (hash-table (pat pat) ...+ ooo) — 이 패턴도 부정확할 수 있어서 더 이상 권장되지 않습니다. 하지만 많은 프로그램이 그 부정확한 동작에 의존하므로, 이전 버전 호환성을 위해 여전히 제공합니다. hash-table을 일반화해서 마지막 반복 패턴을 지원합니다.

    > (match #hash(("a" . 1) ("b" . 2))
        [(hash-table (key val) ...) key])
    '("b" "a")
    
  • (cons pat1 pat2) — 페어 값을 일치시켜요.

    > (match (cons 1 2)
        [(cons a b) (+ a b)])
    3
    
  • (mcons pat1 pat2) — 가변 페어 값을 일치시켜요.

    > (match (mcons 1 2)
        [(cons a b) 'immutable]
        [(mcons a b) 'mutable])
    'mutable
    
  • (box pat) — 박스된 값을 일치시켜요.

    > (match #&1
        [(box a) a])
    1
    
  • (struct-id pat ...) 또는 (struct struct-id (pat ...))struct-id라는 이름의 구조체 타입의 인스턴스와 일치하는데, 인스턴스의 각 필드가 대응하는 pat와 일치해야 해요. struct*도 함께 보세요. 보통 struct-idstruct로 정의됩니다. 더 일반적으로 struct-id는 구조체 타입의 확장 시간 정보에 바인딩되어야 하는데(Structure Type Transformer Binding 참조), 그 정보는 필드 pat 수에 대응하는 술어 바인딩과 필드 접근자 바인딩을 적어도 포함합니다. 특히 모듈 import나 struct 선언을 포함한 서명을 가진 unit import가 구조체 타입 정보를 제공할 수 있어요.

    예시:

    (struct tree (val left right))
    > (match (tree 0 (tree 1 #f #f) #f)
        [(tree a (tree b  _ _) _) (list a b)])
    '(0 1)
    
  • (struct struct-id _) — 인스턴스 필드의 내용에 관계없이 struct-id의 어떤 인스턴스와도 일치해요.

  • (regexp rx-expr)rx-expr이 만든 regexp 패턴과 일치하는 문자열과 일치합니다. rx-exprregexp, pregexp, byte-regexp, byte-pregexp, 문자열, 또는 바이트 문자열일 수 있어요. 문자열과 바이트 문자열 값은 각각 regexpbyte-regexp를 사용해 패턴으로 변환됩니다. regexp에 대한 자세한 내용은 Regular Expressions 참조.

    예시:

    > (match "apple"
        [(regexp #rx"p+") 'yes]
        [_ 'no])
    'yes
    > (match "banana"
        [(regexp #px"(na){2}") 'yes]
        [_ 'no])
    'yes
    > (match "banana"
        [(regexp "(na){2}") 'yes]
        [_ 'no])
    'no
    > (match #"apple"
        [(regexp #rx#"p+") 'yes]
        [_ 'no])
    'yes
    > (match #"banana"
        [(regexp #px#"(na){2}") 'yes]
        [_ 'no])
    'yes
    > (match #"banana"
        [(regexp #"(na){2}") 'yes]
        [_ 'no])
    'no
    
  • (regexp rx-expr pat)regexp-match의 결과가 pat를 상대로 일치되는, regexp 형식을 확장해 일치를 더 제약합니다.

    > (match "apple"
        [(regexp #rx"p+(.)" (list _ "l")) 'yes]
        [_ 'no])
    'yes
    > (match "append"
        [(regexp #rx"p+(.)" (list _ "l")) 'yes]
        [_ 'no])
    'no
    
  • (pregexp rx-expr) 또는 (pregexp rx-expr pat)regexp 패턴과 같지만, rx-exprpregexp, byte-pregexp, 문자열, 또는 바이트 문자열이어야 합니다. 문자열과 바이트 문자열 값은 각각 pregexpbyte-pregexp를 사용해 패턴으로 변환됩니다.

  • (and pat ...) — 모든 pat가 일치하면 일치합니다. 이 패턴은 (and id pat)처럼 써서 idpat와 일치하는 전체 값에 바인딩하는 데 자주 쓰여요. pat들은 나타난 순서대로 일치됩니다.

    > (match '(1 (2 3) 4)
      [(list _ (and a (list _ ...)) _) a])
    '(2 3)
    
  • (or pat ...) — 어느 pat라도 일치하면 일치합니다. 각 pat는 같은 식별자 집합을 바인딩해야 해요.

    > (match '(1 2)
      [(or (list a 1) (list a 2)) a])
    1
    
  • (not pat ...) — 어떤 pat도 일치하지 않을 때 일치하며, 어떤 식별자도 바인딩하지 않아요.

    > (match '(1 2 3)
      [(list (not 4) ...) 'yes]
      [_ 'no])
    'yes
    > (match '(1 4 3)
      [(list (not 4) ...) 'yes]
      [_ 'no])
    'no
    
  • (app expr pats ...) — 일치할 값에 expr을 적용하고, 적용의 각 결과는 각각 pats 중 하나를 상대로 일치해요.

    > (match '(1 2)
      [(app length 2) 'yes])
    'yes
    > (match "3.14"
      [(app string->number (? number? pi))
       `(I got ,pi)])
    '(I got 3.14)
    > (match '(1 2)
      [(app (lambda (v) (split-at v 1)) '(1) '(2)) 'yes])
    'yes
    > (match '(1 2 3)
      [(app (λ (ls) (apply values ls)) x y (? odd? z))
       (list 'yes x y z)])
    '(yes 1 2 3)
    
  • (? expr pat ...) — 일치할 값에 expr을 적용하고, 결과가 참 값인지 검사하며, 추가 pat들도 일치해야 합니다. 즉 ?는 술어 적용과 and 패턴을 결합해요. 다만 ?and와 달리 expr이 어떤 pat보다 먼저 일치하도록 보장합니다. expr 프로시저는 동일한 입력에 대해 두 번 이상 호출될 수 있고(드물게지만), expr 호출이 이루어지는 순서는 의존해서는 안 됩니다.

    > (match '(1 3 5)
      [(list (? odd?) ...) 'yes])
    'yes
    
  • (quasiquote qp) — 준인용(quasipattern)을 도입하는데, 식별자가 심볼과 일치합니다. quasiquote 표현식 형식처럼 unquoteunquote-splicing은 일반 패턴으로 다시 이스케이프합니다.

    > (match '(1 2 3)
       [`(1 ,a ,(? odd? b)) (list a b)])
    '(2 3)
    
  • derived-patterndefine-match-expander를 통한 매크로 확장으로 정의된 패턴과 일치해요.

참고로 일치 과정은 입력을 여러 번 구조 분해할 수 있고, (app expr pat) 같은 패턴에 내장된 표현식을 임의의 순서로 또는 여러 번 평가할 수 있어요. 따라서 그런 표현식은 여러 번 호출해도 안전하거나, 원래 프로그램에 나타난 순서와 다른 순서로 호출해도 안전해야 합니다.

버전 8.9.0.5 (패키지 base)에서 변경: #:do 지원이 추가됨.

버전 8.11.1.10에서 변경: hashhash* 패턴이 추가됨.

추가 매칭 형식 (Additional Matching Forms)

문법

(match* (val-expr ...+) clause* ...)

clause* = [(pat ...+) option=> option ... body ...+]

값의 수열을 각 절에 순서대로 일치시키며, 절의 모든 패턴이 일치할 때만 일치합니다. 각 절은 val-expr 수와 같은 수의 패턴을 가져야 해요.

예시:

> (match* (1 2 3)
    [(_ (? number?) x) (add1 x)])
4
> (match* (15 17)
    [((? number? a) (? number? b))
     #:when (= (+ a 2) b)
     'diff-by-two])
'diff-by-two

문법

(match/values expr clause* clause* ...)

exprn개의 값으로 평가되면, 모든 n개의 값을 clause* ...의 패턴들을 상대로 일치시켜요. 각 절은 정확히 n개의 패턴을 담아야 합니다. expr에서 기대할 값의 개수를 결정하려면 절이 적어도 하나 필요합니다.

예시:

> (match/values (values 1 2 3)
    [(a (? number? b) (? odd? c)) (+ a b c)])
6

문법

(define/match (head args) match*-clause ...)

head = id | (head args)

args = arg ... | arg ... . rest-id

arg = arg-id | [arg-id default-expr] | keyword arg-id | keyword [arg-id default-expr]

match*-clause = [(pat ...+) option=> option ... body ...+]

idmatch*를 사용하는 패턴 매칭 절로 정의된 프로시저에 바인딩합니다. 각 절은 함수 머리의 인자에 대응하는 패턴 수열을 가져요. 인자는 매칭 목적을 위해 함수 머리에 나타난 순서대로 정렬됩니다.

예시:

(define/match (fact n)
  [(0) 1]
  [(n) (* n (fact (sub1 n)))])
> (fact 5)
120

함수 머리는 선택적 또는 키워드 인자를 포함할 수 있고, 커리된 인자를 가질 수 있으며, rest 인자를 포함할 수도 있습니다.

예시:

(define/match ((f x) #:y [y '(1 2 3)])
  [((regexp #rx"p+") `(,a 2 3)) a]
  [(_ _) #f])
> ((f "ape") #:y '(5 2 3))
5
> ((f "dog"))
#f
(define/match (g x y . rst)
  [(0 0 '()) #t]
  [(5 5 '(5 5)) #t]
  [(_ _ _) #f])
> (g 0 0)
#t
> (g 5 5 5 5)
#t
> (g 1 2)
#f

문법

(match-lambda clause ...)

(match-λ clause ...)

(lambda (id) (match id clause ...))와 동등합니다.

버전 8.13.0.5 (패키지 base)에서 변경: match-λ 추가됨.

문법

(match-lambda* clause ...)

(match-λ* clause ...)

(lambda lst (match lst clause ...))와 동등합니다.

버전 8.13.0.5 (패키지 base)에서 변경: match-λ* 추가됨.

문법

(match-lambda** clause* ...)

(match-λ** clause* ...)

clause*에 나타난 패턴 수에서 args ...의 개수를 계산해 (lambda (args ...) (match* (args ...) clause* ...))와 동등합니다.

버전 8.13.0.5 (패키지 base)에서 변경: match-λ** 추가됨.

문법

(match-let ([pat expr] ...) body ...+)

let을 일반화해 패턴 바인딩을 지원합니다. 각 expr은 대응하는 pat와 일치되고(일치는 반드시 성공해야 함), pat이 도입한 바인딩은 body들에 보입니다.

예시:

> (match-let ([(list a b) '(1 2)]
              [(vector x ...) #(1 2 3 4)])
    (list b a x))
'(2 1 (1 2 3 4))

문법

(match-let* ([pat expr] ...) body ...+)

match-let과 같지만 let*을 일반화해서, 각 pat의 바인딩이 각각의 이후 expr에서 사용 가능합니다.

예시:

> (match-let* ([(list a b) '(#(1 2 3 4) 2)]
               [(vector x ...) a])
    x)
'(1 2 3 4)

문법

(match-let-values ([(pat ...) expr] ...) body ...+)

match-let과 같지만 let-values를 일반화합니다.

문법

(match-let*-values ([(pat ...) expr] ...) body ...+)

match-let*과 같지만 let*-values를 일반화합니다.

문법

(match-letrec ([pat expr] ...) body ...+)

match-let과 같지만 letrec을 일반화합니다.

문법

(match-letrec-values ([(pat ...) expr] ...) body ...+)

match-let과 같지만 letrec-values를 일반화합니다.

버전 6.1.1.8 (패키지 base)에서 추가됨.

문법

(match-define pat expr)

pat가 바인딩하는 이름들을 expr의 결과를 상대로 일치시켜 나온 값들에 정의합니다.

예시:

> (match-define (list a b) '(1 2))
> b
2

문법

(match-define-values (pat pats ...) expr)

match-define과 같지만 expr이 여러 값을 만들 때를 위한 것입니다. match/values처럼 기대할 값의 개수를 결정하려면 패턴이 적어도 하나 필요합니다.

예시:

> (match-define-values (a b) (values 1 2))
> b
2

프로시저

(exn:misc:match? v)boolean?

v : any/c

일치 실패의 경우 발생하는 예외에 대한 술어예요.

문법

failure-cont

현재 패턴이 실패한 것처럼 매칭을 계속합니다. => 형식의 사용과 달리 이것은 현재 맥락을 이스케이프하지 않으므로, match 형식에 대해 꼬리 위치에서만 써야 합니다.

match 확장하기 (Extending match)

문법

(define-match-expander id proc-expr)

(define-match-expander id proc-expr proc-expr)

id를 *매치 확장기(match expander)*에 바인딩합니다.

첫 번째 proc-expr 하위 표현식은 matchpat를 만드는 변환기(transformer)로 평가되어야 해요. id가 패턴의 시작으로 나타날 때마다 이 변환기는 확장 시간에 전체 패턴(id 포함)에 해당하는 문법 객체를 받습니다. 패턴은 변환기의 결과로 대체됩니다.

두 번째 proc-expr 하위 표현식이 만든 변환기는 id가 표현식 맥락에서 사용될 때 사용됩니다. 두 번째 proc-expr을 사용하면 id가 패턴 안팎 모두에서 의미를 가질 수 있어요.

매치 확장기는 id가 수열의 첫 번째 위치에 나타나지 않으면 호출되지 않습니다. 대신 define-match-expander가 바인딩한 식별자는 수열의 첫 번째 위치 외의 어디에 나타나면 (다른 식별자처럼) 바인딩 식별자로 사용됩니다.

예를 들어 패턴 매처를 확장하고 문법 리스트를 구조 분해하려면:

(define (syntax-list? x)
  (and (syntax? x)
       (list? (syntax->list x))))

(define-match-expander syntax-list
  (lambda (stx)
    (syntax-case stx ()
      [(_ elts ...)
       #'(? syntax-list?
            (app syntax->list (list elts ...)))])))

(define (make-keyword-predicate keyword)
  (lambda (stx)
    (and (identifier? stx)
         (free-identifier=? stx keyword))))

(define or-keyword? (make-keyword-predicate #'or))
(define and-keyword? (make-keyword-predicate #'and))
> (match #'(or 3 4)
    [(syntax-list (? or-keyword?) b c)
     (list "OOORRR!" b c)]
    [(syntax-list (? and-keyword?) b c)
     (list "AAANND!" b c)])
'("OOORRR!" #<syntax:eval:88:0 3> #<syntax:eval:88:0 4>)
> (match #'(and 5 6)
    [(syntax-list (? or-keyword?) b c)
     (list "OOORRR!" b c)]
    [(syntax-list (? and-keyword?) b c)
     (list "AAANND!" b c)])
'("AAANND!" #<syntax:eval:89:0 5> #<syntax:eval:89:0 6>)

그리고 여기 define-match-expander가 바인딩한 식별자가 패턴 수열의 첫 번째 위치에 나타나지 않으면 특별히 취급되지 않는다는 예시가 있어요. 다음 (잘못된) 길이 함수 정의를 보죠:

(define-match-expander nil
  (λ (stx) #''())
  (λ (stx) #''()))

(define (len l)
  (match l
    [nil 0]
    [(cons hd tl) (+ 1 (len tl))]))

nil 주위에 괄호가 없기 때문에, match는 첫 번째 경우를 매치 확장기의 사용이 아니라 식별자(무엇이든 일치하는)로 취급하고, len은 항상 0을 반환해요.

> (len nil)
0
> (len (cons 1 nil))
0
> (len (cons 1 (cons 2 nil)))
0

매치 확장기는 첫 요소가 확장기에 바인딩된 identifier?인 어떤 문법 페어든 받아들입니다. 다음 예시는 (expander a b . rest) 형태의 improper 문법 리스트로 호출할 수 있는 매치 확장기를 보여 줍니다.

(define-match-expander my-vector
  (λ (stx)
    (syntax-case stx ()
      [(_ pat ...)
       #'(vector pat ...)]
      [(_ pat ... . rest-pat)
       #'(app vector->list (list-rest pat ... rest-pat))])))
> (match #(1 2 3 4 5)
   [(my-vector a b . rest)
     (list->vector (append rest (list a b)))])
'#(3 4 5 1 2)

버전 7.7.0.2 (패키지 base)에서 변경: 매치 확장기가 이제 첫 요소가 확장기에 바인딩된 identifier?인 어떤 문법 페어든 허용함. 위 예시는 이전 버전에서 동작하지 않았습니다.

prop:match-expander : struct-type-property?

define-match-expander가 만든 것처럼 매치 확장기로 동작하는 구조체 타입을 식별하는 구조체 타입 프로퍼티예요.

프로퍼티 값은 정확한 음이 아닌 정수이거나 인자가 하나 또는 둘인 프로시저여야 합니다. 전자의 경우 그 정수는 구조체 안에서 프로시저를 담아야 하는 필드를 지정하는데, 정수는 0(포함) 이상이고 구조체 타입의 (슈퍼타입 필드를 세지 않은) 비자동 필드 수(미포함) 미만이어야 하며, 지정된 필드는 불변으로도 명시되어야 합니다.

프로퍼티 값이 인자 하나의 프로시저이면, 그 프로시저가 매치 확장용 변환기 역할을 합니다. 프로퍼티 값이 인자 둘의 프로시저이면, 첫 번째 인자는 prop:match-expander 프로퍼티를 가진 타입의 구조체이고, 두 번째 인자는 매치 확장기에서와 같은 문법 객체예요.

프로퍼티 값이 할당 변환기(assignment transformer)이면, 호출되기 전에 set!-transformer-procedure로 감싸진 프로시저가 추출됩니다.

이 바인딩은 for-syntax로 제공됩니다.

prop:legacy-match-expander : struct-type-property?

prop:match-expander와 같지만, 구식(legacy) 매치 문법을 위한 것입니다.

이 바인딩은 for-syntax로 제공됩니다.

프로시저

(match-expander? v)boolean?

v : any/c

(legacy-match-expander? v)boolean?

v : any/c

적절한 매치 확장기 프로퍼티를 구현하는 값에 대한 술어들이에요.

프로시저

(syntax-local-match-introduce stx)syntax?

stx : syntax?

이전 버전 호환성 전용이며, syntax-local-introduce와 동등합니다.

버전 6.90.0.29 (패키지 base)에서 변경: syntax-local-introduce와 동등하게 만듦.

파라미터

(match-equality-test)(any/c any/c . -> . any)

(match-equality-test comp-proc)void?

comp-proc : (any/c any/c . -> . any)

식별자의 여러 사용이 "같은" 값과 일치하는지 검사하는 데 쓰는 비교 프로시저를 결정하는 파라미터예요. 기본값은 equal?입니다.

문법

(match/derived val-expr original-datum clause ...)

(match*/derived (val-expr ...) original-datum clause* ...)

각각 matchmatch*와 같지만, 형식 안의 모든 문법 오류의 소스로 쓰일 하위 표현식을 포함합니다. 예를 들어 match-lambdamatch/derived로 확장되어, 형식 본문의 오류가 match 대신 match-lambda로 보고됩니다.

비선형 패턴과 생략점 (Non-linear Patterns and Ellipses)

같은 식별자(비선형 패턴, non-linear pattern) 가 패턴 안에서 여러 번 사용되면, 각 사용처는 같은 ... 안에 있어야 합니다. 그렇지 않으면 문법 오류가 발생해요. 전체 패턴이 일치하려면 각 사용처가 (match-equality-test)에 따라 같은 값과 일치해야 합니다.

식별자의 두 사용처가 같은 ... 아래에 있으면, 패턴의 각 반복은 그 반복 안의 사용처 사이의 동등성을 검사하지만, 식별자는 서로 다른 반복에서 서로 다른 값과 일치할 수 있어요. 식별자는 일치한 값들의 리스트에 바인딩됩니다.

예를 들어 (list (list a a) ...)'((1 1) (2 2) (3 3))와 성공적으로 일치하는데, 각 반복 안에서 두 a가 같기 때문이에요. 첫 번째 반복에서 a1, 두 번째에서 2, 세 번째에서 3이어도 말이죠. 이 경우 오른쪽에서 a'(1 2 3)에 바인딩됩니다.

예시:

; each pair must have equal elements
> (match '((1 1) (2 2) (3 3))
    [(list (list a a) ...) a]
    [_ 'no])
'(1 2 3)

; second pair doesn't match: 2 != 3
> (match '((1 1) (2 3) (3 3))
    [(list (list a a) ...) a]
    [_ 'no])
'no

식별자가 서로 다른 생략점 깊이에서 사용되면 — 예를 들어 한 번은 ... 바깥에, 한 번은 안에, 또는 서로 다른 ... 패턴 아래에 있을 때 — 문법 오류가 발생합니다.

예시:

> (match '(1 1 1 1) [(cons t (list t ...)) t])
eval:99:0: match: non-linear pattern used in `match` with `...`
  in: t
> (match '(1 2 3 4) [(list a ... a) a])
eval:100:0: match: non-linear pattern used in `match` with `...`
  in: a
> (match '((1 2) 3) [(list (list a ...) a) a])
eval:101:0: match: non-linear pattern used in `match` with `...`
  in: a

버전 9.1.0.9 (패키지 base)에서 변경: ... 아래 비선형 패턴에 대한 동등성 검사가 추가되고, 생략점 깊이가 다른 비선형 패턴은 문법 오류를 일으키도록 변경됨.

라이브러리 확장 (Library Extensions)

문법

(== val comparator)

(== val)

comparator로 비교했을 때 일치한 값이 val과 같은지 검사하는 매치 확장기예요. comparator가 제공되지 않으면 equal?로 기본 설정됩니다.

예시:

> (match (list 1 2 3)
    [(== (list 1 2 3)) 'yes]
    [_ 'no])
'yes
> (match (list 1 2 3)
    [(== (list 1 2 3) eq?) 'yes]
    [_ 'no])
'no
> (match (list 1 2 3)
    [(list 1 2 (== 3 =)) 'yes]
    [_ 'no])
'yes

문법

(struct* struct-id ([field pat] ...))

struct-id라는 이름의 구조체 타입의 인스턴스와 일치하는 match 패턴 형식인데, 인스턴스의 필드 field가 대응하는 pat와 일치합니다. 필드에는 슈퍼 타입에서 온 것은 포함되지 않아요. struct-id의 어떤 필드든 생략할 수 있고, 그런 필드는 어떤 순서로든 나타날 수 있습니다.

예시:

(struct tree (val left right))
(struct tree* tree (val))
> (match (tree 0 (tree 1 #f #f) #f)
    [(struct* tree ([val a]
                    [left (struct* tree ([right #f] [val b]))]))
     (list a b)])
'(0 1)
> (match (tree* 0 #f #f 42)
    [(and (struct* tree* ([val a]))
          (struct* tree ([val b])))
     (list a b)])
'(42 0)

더 알아보기