From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Michael Heerdegen Newsgroups: gmane.emacs.devel Subject: Re: The poor state of documentation of pcase like things. Date: Sat, 19 Dec 2015 16:31:07 +0100 Message-ID: <87bn9mtpz8.fsf@web.de> References: <20151216202605.GA3752@acm.fritz.box> NNTP-Posting-Host: plane.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: ger.gmane.org 1450539088 13167 80.91.229.3 (19 Dec 2015 15:31:28 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Sat, 19 Dec 2015 15:31:28 +0000 (UTC) Cc: Alan Mackenzie , Emacs developers To: Kaushal Modi Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Sat Dec 19 16:31:20 2015 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by plane.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1aAJTb-0001M1-LG for ged-emacs-devel@m.gmane.org; Sat, 19 Dec 2015 16:31:19 +0100 Original-Received: from localhost ([::1]:37683 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aAJTa-0001zL-VU for ged-emacs-devel@m.gmane.org; Sat, 19 Dec 2015 10:31:18 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:58091) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aAJTW-0001z0-Px for emacs-devel@gnu.org; Sat, 19 Dec 2015 10:31:15 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1aAJTT-00028q-Dg for emacs-devel@gnu.org; Sat, 19 Dec 2015 10:31:14 -0500 Original-Received: from mout.web.de ([212.227.15.3]:51988) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aAJTT-00028g-0z for emacs-devel@gnu.org; Sat, 19 Dec 2015 10:31:11 -0500 Original-Received: from drachen.dragon ([90.186.0.107]) by smtp.web.de (mrweb003) with ESMTPSA (Nemesis) id 0MPpD8-1a6Rqw3Eva-005319; Sat, 19 Dec 2015 16:31:09 +0100 In-Reply-To: (John Wiegley's message of "Thu, 17 Dec 2015 08:34:27 -0800") User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.0.50 (gnu/linux) X-Provags-ID: V03:K0:96Z69ma9l6GzVr+BW475OElS+JOU4Jn9Mu+57mYd8nz89Y7tQHC +N5G4CMQVrkE+FPhnPsdFbVWDhgZ4iPsGRDvTWCjXPLbQwqMn8IEx+c5WOOT/C8t37QG60V Jrqo1gFSrgEsccoIwXSnZ9h6t8CfIysRExTe8ASl8/lb6vMquVSOAgqP1KE2yIT4dJzM8Rg EVJTELQiTDYTTRvzCtZWw== X-UI-Out-Filterresults: notjunk:1;V01:K0:1WB7fcntf6M=:d/qeEG6KEVzdau+tmO2z3f wJm2aHSuYbo19LiULBNW9b5f6RuClOp3Iytyf9ZMO966329yteeMJl6q1pY7IIscjnkzHnOIG 9O1zroctFxlUh1xa7amW5TtY/1wZ8DWXKOkrambJHepoCCQbLwSpwTkQ86MHTe+TeLDZklF4R 1YO0sU05DQhGsdRthhNvaDfDQO328RrG60OlswI4CQ2gFDAFozLzO2DEV2lnj3xRUj1TuFJ0q IT96uTy7Bga+OHxHpIME41ax1Yu7y8cHyV2vMc2FsOl7QUrbqQU4qEs42E4o1qqmfR3xnZDIV 3fYfaDC64MWcjkG/aOEfO93y1igz8/VZSF7Zqzc5fvCpkuVoujD1EC3cqd9QFnV4jDAQVMwXd udGMobh/Vml3x62wdtonDCrsht3C3xke7b2zwHmfZUebr5njeQg6VvjXATYpRCUWSMsCuqXxd JgZ1ZRqYNc2BUhuQBFJ3gqrW7OCsyaknsxbESEjen+QcWJVxmo3rr2ornxg4Qh80KccfHvpwb N1JhkvNQm3adWkd0mhJduCON5UQM/MjBZvkpR+drMJ5FB0vIqPmzR9Fh+6Fu1HUV3u8y+CSsv bK41RKIiz3MRetnrX8v319bW4XM7mEWbObpcbvrgV2xuB/VhBA1DyyRBzCJqHC1SFDHAmxpMN CrY834Zd63f7ORnYZjIWScEEMpvq7yJV7QS9KsgAFzyMngEM6JpvPf2qosOiC3E7aCFvc1LgM /tUYCCCSe6Siqu3dquXRUaVcOWWVw4DxDcz2+6uGvZ9uZQZFEuwj300y+ao= X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 212.227.15.3 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.devel:196506 Archived-At: --=-=-= Content-Type: text/plain John Wiegley writes: > Since pattern matching like this isn't something I had ever encountered > outside of FP, I agree that a tutorial is in order. I'm willing to > volunteer > for this. I had already posted a tutorial here some time ago. Sorry, my fault, I did not yet find the time to further improve it and work in the comments. Here it is again: --=-=-= Content-Type: application/emacs-lisp Content-Disposition: inline; filename=pcase-guide.el Content-Transfer-Encoding: quoted-printable ;; Understand `pcase' now! - A trivial guide to a powerful tool ;; =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D ;; From the docstring: ;; (pcase EXP &rest CASES) ;; Perform ML-style pattern matching on EXP. ;; What's "ML-style pattern matching"? If you know ML, pcase will ;; probably seem familiar to you. I don't; it isn't necessary to ;; understand `pcase'. ;; Any `pcase' form looks like this: (pcase EXPRESSION (PATTERN CODE ...) ...) ;; The EXPRESSION is first evaluated. Unlike the similar `cond', you ;; specify PATTERNs instead of CONDITIONs, and the PATTERNS are tested ;; for whether they match the evaluated EXPRESSION, in order. If one ;; PATTERN matches, it's CODE is evaluated. Like in `cond', all ;; branches are tested until one is found whose PATTERN matches. All ;; remaining branches are ignored. ;; ;; The PATTERNs are not lisp code, like `cl-loop' forms they are ;; written with an own simple language. There are a few simple basic ;; patterns, and you can combine them. ;; ;; A very useful thing is that matching a pattern can bind variables ;; as a side effect. This bindings are in effect when the CODE of ;; that branch is executed (actually, they are already in effect when ;; then the remaining parts of the pattern are processed; that fact ;; will prove useful later in this guide). ;; ;; All yet to understand is the semantic each of the basic PATTERNs. ;; Since you can do quite a lot with `pcase' - e.g. you can use it ;; like `let*', like `cond', like `case', like `destructuring-bind', ;; all in combination and more, it is clear that the semantic can't be ;; absolutely trivial. However, there are only very few basic forms ;; of patterns, and all are easy to understand. And those forms can ;; be combined. That's already it. I will explain the basic patterns ;; one after the other, you may want to take one or two breaks in ;; between. ;; ;; After you have read this, you will want to practise a bit with ;; self-written examples, or try to understand occurrances in the ;; sources, so that you get used to the different forms of patterns. ;; After that, you will never have to read this guide again, just ;; consult C-h f pcase RET and refresh your memories for 10 seconds or ;; so, that will suffice. ;; ;; So, let's look at the patterns! (BTW, you can define more patterns ;; with `pcase-defmacro', so C-h f pcase may list some more patterns ;; than described here.) Here are the built-in basic patterns: ;; ;; _ matches anything. ;; SYMBOL matches anything and binds it to SYMBOL. ;; (or PAT...) matches if any of the patterns matches. ;; (and PAT...) matches if all the patterns match. ;; 'VAL matches if the object is =E2=80=98equal=E2=80=99 to VAL. ;; ATOM is a shorthand for 'ATOM. ;; when ATOM is a keyword, an integer, or a string. ;; (pred FUN) matches if FUN applied to the object returns non-nil. ;; (guard BOOLEXP) matches if BOOLEXP evaluates to non-nil. ;; (let PAT EXP) matches if EXP matches PAT. ;; (app FUN PAT) matches if FUN applied to the object matches PAT. ;; ;; `QPAT Backquote-style pcase patterns. ;; That's just copied from the pcase docstring. The explanations are ;; already sufficient. Let's start with some examples for 'VAL and _ (pcase x ('a 1) ('"Hallo" 2) (_ 3)) ;; This will return 1 if x is bound to the symbol `a', 2 if x is bound ;; to a string equal to "Hallo", and 3 else. Hey, already two pattern ;; types learned! ;; ;; A SYMBOL (different from `_') "matches anything and binds it to ;; SYMBOL.". Example: (pcase x ('a 1) ('"Hallo" 2) (thing (message "%s is neither equal to 'a nor to \"Hallo\"." thing))) ;; If you use 'VAL, and VAL is a keyword, an integer, or a string, you ;; can leave out the quote (but only in these cases!) - in the above ;; example: (pcase x ('a 1) ("Hallo" 2) (thing (message "%s is neither equal to 'a nor to \"Hallo\"." thing))) ;; that's the case only for the string "Hallo" (why would it not be ;; useful to leave out the quote before the symbol `a'? ;; (or PAT...) matches if any of the patterns matches. Example: (pcase (car x) ((or 'a "Hallo") t) (_ nil)) ;; This will return t if the car of x references either the symbol `a' ;; or a string equal to "Hallo", and nil else. ;; ;; Now you've already learned 5 out of 11 basic types of patterns. ;; The remaining types are similarly easy. ;; (pred FUN) matches if FUN applied to the object returns non-nil. ;; "Object" just means the EXPRESSION that is matched. ;; ;; (and PAT...) matches if all the patterns match. (pcase x ((pred listp) 1) ((or (pred arrayp) (pred numberp)) 2) ((and (pred stringp) (pred (lambda (x) (string-match-p "blue" x)))) 3)) ;; This will return 1 if x is bound to a list, 2 if what x is bound to ;; fulfils eihter `arrayp' or `numberp'. And 3 if the binding of x is ;; a string that contains "blue". If none of that is true, no branch ;; matches, and `pcase' returns nil. ;; If matching any pattern established a variable binding, you can ;; refer to these bindings in the "following" patterns in that branch. ;; (guard BOOLEXP) matches if BOOLEXP evaluates to non-nil. (pcase thing ((and (pred stringp) string (guard (< (length string) 60))) (message "A short string: %S" string)) ((pred stringp) (message "A quite long string.")) (it (message "No string: %s" it))) ;; What's `it'? Oh, just a random SYMBOL name. ;; ;; A common pitfall is that you want to test a condition, but forget ;; to wrap it into `guard' because you used `cond' for the half of ;; your life. This will happen, so be warned. Always remember that ;; pcase is about "matching", though you _can_ test conditions as a ;; strange special case of matching via the `guard' pattern. ;; Exercise: We noted that `pcase' can be used to substitute `let*', ;; `cond' or `case' in your code. How would you roughly describe how ;; one can rewrite an arbitrary `let*', `cond' or `case' form using ;; `pcase'? (Hint: It's not hard.) ;; (let PAT EXP) matches if EXP matches PAT. ;; ;; Don't confuse this with the `let' special form. ;; ;; $$$$$FIXME: try to make that paragraph more clear, and provide an ;; easier example. ;; ;; This is a pattern form that allows you to match a pattern PAT ;; against an _arbitrary_ expression EXP. This is not special, ;; matching PAT is done as you have learned, just against the EXP you ;; specify there, and not the EXPRESSION given to pcase at top level. ;; ;; Example: (pcase x ((and (pred numberp) (let (pred (lambda (x) (< 5 x))) (abs x))) t) (_ nil)) ;; This will return t if x is bound to a number whose absolute value ;; is larger than 5, like 7 or -13, and nil else, e.g. for 0 or -2 or ;; "Hallo". ;; Why is it called `let' then? Because you can use it to ;; conveniently bind variables. Example: (pcase string (and (pred stringp) (let l (length string)) (guard (> l 0))) l) ;; will return the string length of STRING if it is bound to a string ;; of non-zero length, and nil else. ;; BTW, the `pred' pattern form allows to simplify things like (pred (lambda (x) (< 5 x))) ;; Instead of (pred FUN) ;; there is the more general form (FUN ARG1 .. ARGn) ;; where FUN gets called with ARG1 .. ARGn as first n args and as ;; n+1'th argument the value being matched. ;; So we can write (pred < 5) instead of (pred (lambda (x) (< 5 x))) ;; For example (pcase x ((and (pred numberp) (let (and (pred < 5) abs-val) (abs x))) abs-val)) ;; will return the absolute value of x if x is a number and its ;; absolute value is larger than 5, and nil else. ;; (app FUN PAT) matches if FUN applied to the object matches PAT. ;; Example: (pcase x ((and (pred stringp) (app length (pred > 60))) t)) ;; will return t if and only if x is bound to a string with length ;; smaller than 60 chars. ;; Now, only one basic pattern form is missing: ;; ;; `QPAT Backquote-style pcase patterns. ;; ;; which allows for destructuring. ;; ;; Let's have a look at the description of this pattern form: ;; ;; -- `QPAT ;; ;; Backquote-style pcase patterns. ;; QPAT can take the following forms: ;; (QPAT1 . QPAT2) matches if QPAT1 matches the car and QPAT2 the c= dr. ;; [QPAT1 QPAT2..QPATn] matches a vector of length n and QPAT1..QPATn ma= tch ;; its 0..(n-1)th elements, respectively. ;; ,PAT matches if the pcase pattern PAT matches. ;; ATOM matches if the object is =E2=80=98equal=E2=80=99= to ATOM. ;; ATOM can be a symbol, an integer, or a string. ;; ;; Remember that, for example, (1 2 3) (1 . (2 3)) ;; desribe the same list structure using two different reader ;; syntaxes. When being read by the Lisp reader, those forms are ;; equal. So, in the above description, the =20=20 (QPAT1 . QPAT2) ;; form can be used to match any kind of arbitrary list, and you don't ;; need to use the read syntax using dot. ;; ;; If you are used to understand grammers: the above description of ;; `QPAT describes a quite simpel grammer. You make like to try it ;; out with some examples yourself, it's simple, though the results ;; might look unfamiliar. It doesn't take long to familiarize with ;; it, though. ;; ;; Note that though the backquote _syntax_ is used here, the backquote ;; macro is never called (!), just it's syntax is used to describe ;; patterns for destructuring because it is convenient to write, and ;; also a bit related to how backquote works. ;; Example: (pcase x (`("foo") t) (`("foo" ,a) a) (`("foo" . ,(and (pred listp) rest)) rest)) ;; This will return t if x is bound to a one element list with the ;; element equal to "foo". ;; ;; If x is bound to a list of two elements, and the first one is equal ;; to "foo", it returns the second element. ;; ;; If x is bound to a cons whose car equals "foo", and the cdr is ;; listp, this cdr is returned. Else, nil is returned. ;; ;; For destructuring, the `_' pattern is handy to match anything. ;; Pitfall: Be sure to unquote `_' if you don't want to match the ;; symbol `_' literally, because _ is not a QPAT. ;; ;; Example: The ;; pattern `(1 ,_ . ,_) ;; matches any list whoose first element is 1, and which has at least ;; one more element. ;; Just like with the "real" backquote, you can use "unquote" in lower ;; levels. Example: `(1 (x ,(and y (guard (< (+ x y) 10))))) ;; This pattern will match any list of two elements, where the first ;; element is 1, and the second argument is a list of two elements; ;; the first element is bound to the variable `x', and the second ;; element is bound to `y', and it is tested whether x + y is smaller ;; than 10. Only then the whole pattern matches. ;; Example: (defun swap-first-elts (thing) (pcase thing ((and (pred listp) `(,a ,b . ,rest)) `(,b ,a . ,rest)) (it it))) ;; This function will check whether the given argument is a list with ;; two or more elements. In this case, it returnes this list with the ;; first two elements swapped. In any other case, it just returns the ;; function argument. ;; ;; Here, again note that in the pattern, the backquote is part of the ;; pattern description: `(,a ,b . ,rest) ;; while in that branch's CODE: `(,b ,a . ,rest) ;; the backquote macro is indeed used. Though these are two ;; completely different things, the expressions look quite similar and ;; natural for the purpose, so you might understand now why backquote ;; and unquote were used to define destructuring pattern syntax. --=-=-= Content-Type: text/plain I'm very much willing to help with a different tutorial, prove-read your stuff and improved docs etc. but don't have so much time before Christmas. Do we have the time to do this after Christmas? Regards, Michael. --=-=-=--