Racket’s match

/ 教程, racket

updated_at: 2022-02-27

说到Racket语言的match,其实它主是用于条件判断的,而谈到条件判断,在当代的编程语言中,最为大家熟知的莫过于if这个关键字了。当然Racket语言中也有if,但是其是最最基础的一个条件判断。其他诸如casecond,亦或本文在后面即将铺展开来讲的match,尤其是match,在Racket语言的条件判断中,因为LISP语言高度同像性的S-expression语法,赋予其了非常多高阶使用方法。

    1 从if谈起

      1.1 #:when/not/regexp在string对比中的使用

      1.2 app在判断list长度中的使用

      1.3 hash-table在hash键值对比中的使用

    2 示例:匹配一段字符串,相对应的去输出Xexpr进而转成HTML

      2.1 使用cond的写法

      2.2 使用match的#:when的写法

      2.3 使用match的regexp的写法

      2.4 使用match的app的写法

1  从if谈起🔗

2023年更正:
没有if-not的原因,更可能是因为(if-not a b c)总是可以被写成(if a c b)的,而其他的lisp方言有if-not,可能是因为他们的语言中,(if a b c)支持缺失c子句写成(if a b),而缺失子句在Racket中是被when覆盖了的:(when a b),而when是有when-not的,称为unless(unless a c)

很早时候,我就在官方文档读到过Choosing the Right Construct这个章节,但只是一略而过,认为没有什么特别大的价值。后来慢慢写的Racket代码多了,有几个关于letif等几个索然无味的疑惑一直停留在脑海里,然后偶然重读了上面章节的文章才恍然大悟。

我在很长一段时间里面都在疑惑,Racket作为一个Scheme语言的流行方言,俨然已经是一个库非常丰富且成熟的语言了,为什么没有条件判断if关键字的否定判断关键字呢?因此在否定时都需要写成:

> (if (not (= (+ 3 2) 5))
  (displayln "result is not 5")
  (displayln "result is 5"))

result is 5

这岂不是很麻烦吗?特别是在一些复杂的条件判断场景下(Clojure好像这些语法糖都是有的),而且如果细想我所说的麻烦,大体上我是说因为这里的not的使用却不能被写成(if-not ... ... ...)而多的一层括号显得代码很不利落。然而其实在官方文档 How to Program Racket: a Style GuideConditionals章节的起始处,其曾有提到说:

Like definitional constructs, conditionals come in many flavors, too. Because cond and its relatives (case, match, etc) now allow local uses of define, you should prefer them over if.

所以大体上文档是说,如果能不用if,我们应该尽量避免它。所以这应该就是Racket语言中默认没有if-not的原因之一,因为惯例上我们应该使用casecond亦或match
> (case (+ 3 2)
    [(5) "result is 5"]
    [else "result is not 5"])

"result is 5"

> (cond
    [(= (+ 3 2) 5) "result is 5"]
    [else "result is not 5"])

"result is 5"

> (match (+ 3 2)
    [(== 5 =) "result is 5"]
    [_ "result is not 5"])

eval:3:0: _: wildcard not allowed as an expression

  in: (_ "result is not 5")

match在做条件判断时,其所能处理的维度远远胜过于if。这在上面提到的官方文档的Conditionals章节有更加明确的阐述和使用建议。

1.1  #:when/not/regexp在string对比中的使用🔗

比如,下面一个正常的if条件判断语句:
> (require racket/string)
> (define name "Yanying Wang")
> (if (not (string-suffix? name "Wang"))
      (printf "Your family name is not Wang")
      (printf "Your family name is Wang"))

Your family name is Wang

用match可以写成:
> (require racket/match
           racket/string)
> (define name "Yanying Wang")
> (match name
  [n #:when (not (string-suffix? n "Wang"))
     (printf "Your family name is not Wang")]
  [_ (printf "Your family name is Wang")])

Your family name is Wang

用match和正则表达则可以写成:
> (require racket/match
           racket/string)
> (define name "Yanying Wang")
; 写法1:
> (match name
  [(not (regexp #rx"Wang$"))
     (printf "Your family name is not Wang")]
  [_ (printf "Your family name is Wang")])

Your family name is Wang

; 写法2:
> (match name
  [(regexp #rx"(?<!Wang)$")
     (printf "Your family name is not Wang")]
  [_ (printf "Your family name is Wang")])

Your family name is Wang

1.2  app在判断list长度中的使用🔗

又如,我们需要判断一个list的长度是否是5,if语句写成:
> (define list1 '(1 4 8 9 5))
> (if (= (length list1) 5)
    (printf "length of 5 list")
    (printf "not a length of 5 list"))

length of 5 list

用match则写成:
> (require racket/match)
> (define list1 '(1 4 8 9 5))
> (match list1
    [(app length 5) (printf "length of 5 list")]
    [_ (printf "not a length of 5 list")])

length of 5 list

如上示例只是一些简单的条件判断,在我们要做更复杂的条件判断的时候,match的优势才会充分显示出来。

1.3  hash-table在hash键值对比中的使用🔗

比如我们有一个HTTP的headers,其中有键值对Accept: application/jsonToken: I am a racketeer,在Racket中以hasheq的数据结构存在,我们需要根据其值来做条件判断:

> (require racket/match)
> (define headers (hasheq 'Accept "application/json" 'Token "I am a racketeer"))
> (match headers
    [(hash-table ('Accept "application/json") ('Token "I am a racketeer")) (printf "correct Accept and Token detected")]
    [(hash-table ('Accept "text/html")) (printf "[Accept: text/html] detected")]
    [(hash-table ('Accept accept-value)) (printf "[Accept: ~a] detected" accept-value)]
    [_ (printf "unknown values")])

correct Accept and Token detected

> (match (hasheq 'Accept "text/html")
    [(hash-table ('Accept "application/json") ('Token "I am a racketeer")) (printf "correct Accept and Token detected")]
    [(hash-table ('Accept "text/html")) (printf "[Accept: text/html] detected")]
    [(hash-table ('Accept accept-value)) (printf "[Accept: ~a] detected" accept-value)]
    [_ (printf "unknown values")])

[Accept: text/html] detected

> (match (hasheq 'Accept "app/html")
    [(hash-table ('Accept "application/json") ('Token "I am a racketeer")) (printf "correct Accept and Token detected")]
    [(hash-table ('Accept "text/html")) (printf "[Accept: text/html] detected")]
    [(hash-table ('Accept accept-value)) (printf "[Accept: ~a] detected" accept-value)]
    [_ (printf "unknown values")])

[Accept: app/html] detected

> (match (hasheq 'abc "abc")
    [(hash-table ('Accept "application/json") ('Token "I am a racketeer")) (printf "correct Accept and Token detected")]
    [(hash-table ('Accept "text/html")) (printf "[Accept: text/html] detected")]
    [(hash-table ('Accept accept-value)) (printf "[Accept: ~a] detected" accept-value)]
    [_ (printf "unknown values")])

unknown values

2  示例:匹配一段字符串,相对应的去输出Xexpr进而转成HTML🔗

我们需要从"大连市气象局发布大风红色预警[IV级/一般]"字符串中提取颜色,并且相对应的输出成HTML文档,比如是:<p class="sssubtext" style="color:Red">大连市气象局发布大风红色预警[IV级/一般]</p>

2.1  使用cond的写法🔗

> (require racket/match racket/string xml)
> (define (gen-html str)
    (define color
      (cond
        [(string-contains? str "蓝色") "color:Blue"]
        [(string-contains? str "黄色") "color:Yellow"]
        [(string-contains? str "橙色") "color:Oringe"]
        [(string-contains? str "红色") "color:Red"]
        [else "color:Black"]))
    (xexpr->string
     `(p ((class "sssubtext") (style ,color))
         ,str)))
> (gen-html "大连市气象局发布大风红色预警[IV级/一般]")

"<p class=\"sssubtext\" style=\"color:Red\">大连市气象局发布大风红色预警[IV级/一般]</p>"

2.2  使用match的#:when的写法🔗

> (require racket/match racket/string xml)
> (define (gen-html str)
    (define color
      (match str
        [str #:when (string-contains? str "蓝色") "color:Blue"]
        [str #:when (string-contains? str "黄色") "color:Yellow"]
        [str #:when (string-contains? str "橙色") "color:Oringe"]
        [str #:when (string-contains? str "红色") "color:Red"]
        [_ "color:Black"]))
    (xexpr->string
     `(p ((class "sssubtext") (style ,color))
         ,str)))
> (gen-html "大连市气象局发布大风红色预警[IV级/一般]")

"<p class=\"sssubtext\" style=\"color:Red\">大连市气象局发布大风红色预警[IV级/一般]</p>"

2.3  使用match的regexp的写法🔗

> (require racket/match racket/string xml)
> (define (gen-html str)
    (define color
      (match str
        [(regexp #rx".*蓝色.*") "color:Blue"]
        [(regexp #rx".*黄色.*") "color:Yellow"]
        [(regexp #rx".*橙色.*") "color:Oringe"]
        [(regexp #rx".*红色.*") "color:Red"]
        [_ "color:Black"]))
    (xexpr->string
     `(p ((class "sssubtext") (style ,color))
         ,str)))
> (gen-html "大连市气象局发布大风红色预警[IV级/一般]")

"<p class=\"sssubtext\" style=\"color:Red\">大连市气象局发布大风红色预警[IV级/一般]</p>"

2.4  使用match的app的写法🔗

> (require racket/match racket/string xml)
> (define (gen-html  str)
    (define color
      (match str
        [(app (lambda (v) (string-contains? v "蓝色")) #t) "color:Blue"]
        [(app (lambda (v) (string-contains? v "黄色")) #t) "color:Yellow"]
        [(app (lambda (v) (string-contains? v "橙色")) #t) "color:Oringe"]
        [(app (lambda (v) (string-contains? v "红色")) #t) "color:Red"]
        [_ "color:Black"]))
    (xexpr->string
     `(p ((class "sssubtext") (style ,color))
         ,str)))
> (gen-html "大连市气象局发布大风红色预警[IV级/一般]")

"<p class=\"sssubtext\" style=\"color:Red\">大连市气象局发布大风红色预警[IV级/一般]</p>"