shadchen vs pcase

:+1:

不过这样也就无法获取匹配到的值了吧?

再来个复杂点的:

(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 已经加载了。

1 个赞

感觉 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")
2 个赞

这个玩法太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

1 个赞

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

pcaseletrxlet 含义本身就不同,前者是直接由 pcase 处理,后者需先由 pcaserx 支持代码处理。

  • pcaselet 的解释是:(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
1 个赞

最好是希望以后能增加一个跟 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)
1 个赞

:smile:失察了,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)

补充一组对照的例子:

Fallthrough

类似 C 语言里 switch-case 的 fallthrough 效果:

switch (expr) {
 case 0:
 case 1:
   // do something
   break;
 }

没找到这么用 pcase / shadchen 的例子,我试了一下,是可以实现的。

  • pcase
(pcase '("foo" "bar")
  ((or `("foo" ,_)
       `(,_ "bar")) t))
;; =>
;; (pcase it
;;   (`("foo" ,_) t)
;;   (`(,_ "bar") t))
;; => t
  • shadchen
(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))

就行

1 个赞

的确。我结论下太早了

开头的 `, 这两个相互抵消了,等于没写。有工具能把这种瑕疵检测出来就好了,比如 pcase 自己展开的时候应该就会发现这种情况。