不过这样也就无法获取匹配到的值了吧?
再来个复杂点的:
(match "bobcatdog"
((concat (and (or "bobcat" "cat") which) "dog")
which)) ;; => "bobcat"
不过这样也就无法获取匹配到的值了吧?
再来个复杂点的:
(match "bobcatdog"
((concat (and (or "bobcat" "cat") which) "dog")
which)) ;; => "bobcat"
对,因为后面没用到(最好避免不必要的变量绑定),如果要用到的话,一个方法:
(pcase '(1 2)
(`(,(and (pred oddp) x) ,_) x))
;; => 1
rx
有支持 pcase
(pcase "bobcatdog"
((rx (let which (or "bobcat" "cat")) "dog") which))
;; => "bobcat"
使用前需要确保 rx
已经加载了。
感觉 pcase + rx 写的代码挺紧凑的:
(pcase "2019-02-27"
((rx (let year (= 4 digit)) "-"
(let month (= 2 digit)) "-"
(let day (= 2 digit)))
(list year month day)))
;; => ("2019" "02" "27")
这个玩法太cool了。
以前想获取带子串正则匹配结果,只能用封装好的s-match。这个用pcase的思路很清奇啊
pcase + rx 的代码确实紧凑,展开之后可读性也好。而 shadchen 展开之后是一大坨,而且 concat 还有 bug,待会去提个 issue,看作者还有没有在维护。
pcase & rx 这样的包,虽然是随着 Emacs 一起发布的,但其实对 Emacs 本身没什么依赖,刚才把 pcase-tests.el 和 rx-tests.el 在低版本的 Emacs 上跑了一遍,就只缺少这三个变量/函数,从 subr.el 抄过来,然后测试用例就全过了:
gensym-counter (var)
gensym (fn)
define-symbol-prop (fn)
如果 GNU ELPA 提供这些包下载,让低版本 Emacs 也能享受到便利。
UPDATE
shadchen 的写法:
(match "2019-02-27"
((concat year "-" month "-" day)
(list year month day))) ;; => ("02" "2019" "27") <-- BUG?
(match "2019-02-27"
((concat year "-" (concat month "-" day))
(list year month day))) ;; => ("2019" "02" "27")
暂时不知如何像 rx 那样进行数字&长度校验,或许要自行扩展实现。
随手扒了下rx的历史。发现1998年就有了。
http://www.ccs.neu.edu/home/shivers/papers/sre.txt
CL的PCRE正则实现,包含一个类rx的基于Sexp描述正则的玩意。
pcase是内置宏,所以有很多配套工具
;; seq.el
(pcase [1 2 3 4 5 6 7 8 9]
;; Each pattern will match each elem, use `&rest' to match the rest part.
((seq (and a (pred (= 1))) b c d &rest rest)
(list a b c d rest)))
;; => (1 2 3 4 [5 6 7 8 9])
;; cl-macs.el
(cl-defstruct human
name age gender)
(pcase (make-human :name "Foo" :age 20 :gender 'male)
;; (cl-struct struct-name (property-name pattern) ...)
((cl-struct human
;; (name (pred (equal "Foo"))) will not catch the variable `name'
;; Don't know why
(name (and name (pred (equal "Foo")))) age gender)
(list name age gender)))
;; => ("Foo" 20 male)
另外,map.el radix-tree.el Old McDonald had a farm EIEIO也有对应的pcase pattern
pcase 其实也早在 2010 年就有了(GIT:emacs-lisp/pcase.el:3621208),但也一直在进化,所以同一份代码,在不同版本的 Emacs 下表现可能会不同,例如:
如果像 cl-lib 这些内置包一样可以单独下载使用,就能减少一些对 Emacs 版本的依赖。
下边这个 let 有点违反直觉:
(pcase '(1 2 3)
(`(1 2 ,(and foo (let (or 3 4) foo)))
foo)) ;; => 3
如果用 memq 来判断,不知道如何把 foo 推到第一个参数位置,就像 (-> ...)
:
(pcase '(1 2 3)
;; (`(1 2 ,(and foo (pred (lambda (n) (memq n '(3 4))))))
(`(1 2 ,(and foo (XXX (memq '(3 4)))))
foo)) ;; => 3
pcase
的 let
和 rx
的 let
含义本身就不同,前者是直接由 pcase
处理,后者需先由 pcase
的 rx
支持代码处理。
pcase
的 let
的解释是:(let PAT EXPR)
,也就是执行 EXPR
,然后用得到的返回值来让 PAT
来匹配。所以我把它理解成重新做一次匹配,所以 (let foo (+ 1 2))
能够匹配且 foo
为 3,是因为在 pcase
中,一个符号匹配任何值;rx
中的 let
貌似可以理解成变量赋值。的确,我觉得 let 和 app 都不是那么好理解,但你的代码没有错,就你的例子,理想的做法是:
(pcase '(1 2 3)
(`(1 2 ,(and (or 3 4) foo)) foo))
;; => 3
如果要用 memq
的话,guard
看起来更简单:
(pcase '(1 2 3)
(`(1 2 ,(and foo (guard (memq foo '(3 4)))))
foo))
;; => 3
最好是希望以后能增加一个跟 pred 相对应的操作,把参数推到第一个位置。
pcase--flip
貌似可行:
(pcase 3
((pred (pcase--flip memq '(1 2 3 4 5)))
"Match"))
;; => "Match"
上面生成的代码比下面的要干净很多:
(pcase 3
((pred (lambda (x) (memq x '(1 2 3 4 5))))
"Match"))
;; => "Match"
使用内部函数不太好吧。
这个函数只互换了 arg1 和 arg2,超过 2 个参数就不适用了:
(pcase "FOO"
((pred (pcase--flip assoc
'(("foo" . 1) ("bar" . 2))
(lambda (s1 s2)
(s-equals? (s-upcase s1) (s-upcase s2)))))
"Match"))
;; => macroexpand: Wrong number of arguments: (3 . 3), 4
可能比较好的方式是给pcase定义新pattern,把elem当做第一个参数传递给pred
pcase 扩展性略显欠缺
pcase当然有扩展性,上面我提到的struct和seq的匹配都不是pcase里写死的。
ELISP> (pcase-defmacro pred-first (pat)
(let ((arg (make-symbol "argname")))
`(pred (lambda (,arg) (,(car pat) ,arg ,@(cdr pat))))))
pred-first--pcase-macroexpander
ELISP> (pcase 'a
((and a (pred-first (memq '(a b c))))
(list a)))
(a)
失察了,pcase 文档里就有提到如何扩展,我应该做点功课再来发言。
可能是由于 let/pred
这些内置的 pattern 并没有使用 pcase-defmacro 来定义(而是统一放在 pcase–macroexpand 递归函数里实现),给我造成了扩展性欠佳的错觉。
APPEND
搜了一下有无其它 pcase
扩展。
我用的 100多个 package 当中一个没有。emacs 源码中有一些:
lisp/emacs-lisp/cl-macs.el 3005: (pcase-defmacro cl-struct (type &rest fields)
lisp/emacs-lisp/eieio.el 348: (pcase-defmacro eieio (&rest fields)
lisp/emacs-lisp/map.el 49: (pcase-defmacro map (&rest args)
lisp/emacs-lisp/radix-tree.el 198: (pcase-defmacro radix-tree-leaf (vpat)
lisp/emacs-lisp/rx.el 1192: (pcase-defmacro rx (&rest regexps)
lisp/emacs-lisp/seq.el 74: (pcase-defmacro seq (&rest patterns)
test/lisp/emacs-lisp/pcase-tests.el 37: (pcase-defmacro pcase-tests-plus (pat n)
补充一组对照的例子:
类似 C 语言里 switch-case 的 fallthrough 效果:
switch (expr) {
case 0:
case 1:
// do something
break;
}
没找到这么用 pcase / shadchen 的例子,我试了一下,是可以实现的。
(pcase '("foo" "bar")
((or `("foo" ,_)
`(,_ "bar")) t))
;; =>
;; (pcase it
;; (`("foo" ,_) t)
;; (`(,_ "bar") t))
;; => t
(match '("foo" "bar")
((or (list "foo" _)
(list _ "bar")) t)
(_ nil))
;; =>
;; (match '("bar" "foo")
;; ((list "foo" _) t)
;; ((list _ "bar") t)
;; (_ nil))
;; => t
shadchen 必须捕捉未匹配的情况(否则报错),略显啰嗦。
EDIT:经楼下提醒,去除了 pcase 例子中多余的 `,
。
直接写
(pcase '("foo" "bar")
((or `("foo" ,_)
`(,_ "bar"))
t))
就行
的确。我结论下太早了
开头的 `,
这两个相互抵消了,等于没写。有工具能把这种瑕疵检测出来就好了,比如 pcase
自己展开的时候应该就会发现这种情况。