Racket语言的match

/ 教程, racket

    1 前言

    2 Choosing the Right Construct

    3 例子1:字符串对比的条件判断

    4 例子2:长度相关的条件判断

    5 例子3:hash键值对比的条件判断

1  前言

粗略算起来,我使用Racket语言来写一些自己的小项目,从刚开始的timable到现在最近在写的http-client,也1年有余了。Racket语言的文档和学习资料基本上都被官方文档所涵盖了,Racket的官方文档在我看来是颇具学术特色的,行文风格很多时候也都会有意无意的偏向于某种概述。另因为其衍生出来的scribble语言和已经定义好了的官方的很多scribble相关的函数,因此在用scribble写库的文档对库函数做说明时会有显而易见的一致性。但我觉得恰恰因为这种风格的偏向,灵活性也变差了,导致了其开发者在写自己库文档的时候,都会优先侧重于相关函数的说明而忽略示例的重要性,这在我看来是不利于快速理解库的实际用法的。和之形成鲜明对比的是我一直在工作中用的Ruby语言,包含着日本人某种极致追求人性化的内涵,其工程化的文档很多时候都会以示例切入,这在实际工程工作中是非常利于快速理解库用法的。

随着我对Racket语言熟练度的提升,慢慢的开始可以理解Racket语言的精髓所在了。通俗来说,随着一个人对编程语言的深入理解,慢慢会发现不同的编程语言之间是有相似性的,比如Ruby/Python,所以基本上来说,一周时间熟悉一下其它语言的语法就可以debug和写另外一个语言的项目代码了。但这时如果跟非常熟悉另外一个语言的人沟通,就会发现你写出来的代码可能会非常不符合这个语言的约定习惯。

所以除非一个语言一开始的时候就是草草了事,并没有弄清楚其自身的定位和目标,否则一个语言之所以存在的价值和精髓,正是它在风格上的差异所衍生出来的固定使用习惯。Racket语言的match,某种程度上在我看来就是lisp足够特别的同像性所衍生出来的强大表达力的具体体现。

2  Choosing the Right Construct

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

我在很长一段时间里面,都在疑惑,Racket作为一个Scheme语言的流行方言,俨然已经是一个库非常丰富且成熟的语言了,为什么没有条件判断if关键字的否定判断关键字呢?因此在否定时都需要写成:
(if (not #t)
  (displayln "false")
  (displayln "true"))
这岂不是很麻烦吗?特别是在一些复杂的条件判断场景下(Clojure好像这些语法糖都是有的)。

但实际上,Racket语言除去casecond之外,还单独抽象出来了更加复杂的match来处理自身语言所可能需要处理的条件判断。match在做条件判断时,其所能处理的维度远远胜过于if。这在上面提到的官方文档的Conditionals章节有更加明确的阐述和使用建议。

3  例子1:字符串对比的条件判断

比如,下面一个正常的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

4  例子2:长度相关的条件判断

又如,我们需要判断一个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的优势才会充分显示出来。

5  例子3: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

至此,不得不惊讶于lisp用一层一层的括号,牺牲掉其他语言所谓的可读性,所换来的强大语法表达力。