emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

pcase-lambda usage [Was: Re: Replace trivial pcase occurrences in the Em


From: Garreau\, Alexandre
Subject: pcase-lambda usage [Was: Re: Replace trivial pcase occurrences in the Emacs sources]
Date: Mon, 29 Oct 2018 19:49:00 +0100
User-agent: Gnus (5.13), GNU Emacs 25.1.1 (i686-pc-linux-gnu, GTK+ Version 3.22.11) of 2017-09-15, modified by Debian

>From what I learnt from pcase-lambda (I wished there were a simple
`match' implemented over `cl-destructuring-bind', similar to
one-matching cl implementations, on which would be based pcase (which
match several alternatively), and pcase-*), here a docstring that seems
better to me, probably not ideal, if anybody can improve, tell it:

#+BEGIN_SRC elisp
  "Like `lambda' but destructure each PATTERN with `pcase'.
It is not self-quoting: the result of `pcase-lambda' is a lambda
expression like returned by `lambda'.  Only then, the lambda expression
may be stored as a function value of a symbol with `fset', passed to
`funcall' or `mapcar', etc.  The first argument, of the form
(PATTERN...) also accepts the usual &optional and &rest keywords
accepted in lambda expressions arglist, but every formal element can be
any pattern accepted by `pcase' (a mere variable name being but a
special case of it).

PATTERN should take the same form as a `pcase' pattern.
  A given subpattern inside PATTERN might not match values (equality) or
  even types, except for each of its subsequences, which *have* to
  exist, have the right type, and be in the right place.
DOCSTRING is an optional documentation string just as in `lambda'.
  Note that each PATTERN that is a special `pcase' pattern will get an
  automatically assigned name: you may want to put
  \\(fn YOUR ARGLIST (AS YOU INTENDED)) so to reflect your patterns.
INTERACTIVE, should as well be a call to the function `interactive'.
It may be omitted.
BODY should be the usual list of Lisp expressions `lambda' takes as
lasts arguments.

\(fn (PATTERN...) [DOCSTRING] [INTERACTIVE] BODY)"
#+END_SRC

As I never saw pcase-lambda before and this was the first time I see
anything about it, here a sample of the process I went trough:

C-h f pcase-lambda RET => (pcase-lambda LAMBDA-LIST &rest BODY)

Okay, so there’s only one pattern, so it’s not pattern matching (as its
name might wrongly suggest), like in OCaml “function” operator, it’s
simple destructuring, exactely like `cl-destructuring-bind', but richer.

Also: no “docstring”? no “interactive”? really? doesn’t `pcase-lambda'
happen to work by translating to a lambda? does it override docstrings
(that might make sense, so to replace the arglist in help by the pcase
pattern) and interactiveness (that doesn’t make sense at all: so we
can’t define after pcase-lambda a pcase-defun and pcase-defmacro (wait,
this one is already occupied… it should have been a different name then:
this has gone too far)?)?

let’s try:
#+BEGIN_SRC emacs-lisp
  (global-set-key (kbd "C-c c c c")
     (pcase-lambda (a `(,b ,c))
       "test"
       (interactive)
       (insert ?2)))
#+END_SRC

Then “C-c c c c”, actually inserts 2.

C-h k C-c c c c => the docstring is “test”.

So these must be implicitly considered as inside “body”… that doesn’t
make really sense: even `lambda' explictly tells about them, while not
recommanding against not using docstring in anonymous function.

Docstring:
> Like ‘lambda’ but allow each argument to be a pattern.
> I.e. accepts the usual &optional and &rest keywords, but every
> formal argument can be any pattern accepted by ‘pcase’ (a mere
> variable name being but a special case of it).

Oh, does that mean I can, like cl-destructuring-bind, put &optional and
&rest wherever I want in any subpattern?  So then pcase is strictly a
superset of cl-destructuring-bind: how sad one is not implemented upon
the other, then (cl-destructuring-bind ought to have a no-error function
so that to be usable to build something upon it).

#+BEGIN_SRC emacs-lisp
  (pcase-lambda (a (b c)) c)
  ;; => pcase--macroexpand: Unknown a pattern: (a (b c))
  
  (pcase (list 1 (list 2 3)) ((a (b c)) c))
  ;; => pcase--macroexpand: Unknown a pattern: (a (b c))
  
  ;; Oh, I recall, pcase behaves differently than normal
  ;; pattern-matching, I must quote everything!
  
  (pcase-lambda '(a (b c)) c)
  ;; => pcase--macroexpand: Unknown a pattern: (a (b c))
  
  ;; Uh, isn’t ' a pattern in pcase? It errs out just the same!
  
  (pcase '(1 (2 3)) ('(a (b c)) t)) ; => nil ; But… why aren’t they
                                             ; behaving the same? Why
                                             ; don’t I get an error?
  
  ;; Oh I recall! I must use “`”:
  (pcase '(1 (2 3)) (`(,a (,b ,c)) t)) ; => t (It Works!™)
  
  So:
  (pcase-lambda `(,a (,b ,c)) c) ; => Wrong type argument: symbolp, (\, a)
  ;; Uh, isn’t that the correct way to use pcase?
  
  ;; Let’s go back before, when it unexpectedly didn’t err, wasn’t I
  ;; right?
  (pcase '(a (b c)) ('(a (b c)) t)) ; => t (so indeed, I was right)
  
  (pcase 'a ('a t)) ; => t (indeed, when you quote you compare symbols,
                    ;       not values (nor bind anything))
  
  ;; So let’s try the simplest case:
  (pcase-lambda 'a a) ; => (lambda (quote a) a)
  
  ;; Oh, if I’m right, that “quote” isn’t a quotation, but an argument
  
  ((lambda (quote a) a) 1 2) ; => 2 (it seems so)
  
  ((lambda (quote a) a) 'a)
  ;; => Wrong number of arguments: (lambda (quote a) a), 1
  ;; it *is* so
  
  ;; Then, if I’m right, the correct usage is:
  
  (pcase-lambda (a) a) ; => (lambda (a) a) ; okay it returns clean
                                           ; lambdas if possible
  
  (pcase-lambda (a (2 3)) a)
  ;; => (lambda (a arg0) (let nil a))
  ;; ah, I was wrong, nevermind (why this useless `let nil'?).  Btw, may I
  ;; suggest an argument name such as `list' instead of `arg0' that would
  ;; indicate type, as it is traditional in lisp (and only number list0,
  ;; list1, etc. if there are several of them)
  
  ((pcase-lambda (a '(2 3)) a) 1 (list 2 3))
  ;; => Invalid function: (pcase-lambda (a (quote (2 3)) a))
  
  ;; oh, there must be a special elisp-related reason why even if it does
  ;; return a lambda it can’t be used right away (maybe because it’s a
  ;; lisp-2?).  Since `lambda' doc says it’s “self-quoting” and “may be
  ;; used as a function”, and that’s not true the same way here, it should
  ;; be said.
  
  (funcall (pcase-lambda (a '(2 3)) a) 1 (list 2 3))
  => 1
  
  ;; Okay so since `lambda' docstring takes the time to say it works with
  ;; `funcall' and `mapcar', then probably `pcase-lambda' should too, so
  ;; an example of how to properly use it comes immediatly to mind.
  
  (funcall (pcase-lambda (a '(2 3)) a) 1 (list 1 1))
  
  ;; Okay it’s not normal, it should give an error, or returns nil, I think.

  (funcall (pcase-lambda (a '(b c)) c) 1 '(2 3)) ; let’s see what’s wrong
  ;; => Symbol’s value as variable is void: c
  ;; c isn’t bound?
  
  ;; Maybe quoting, again?
  (funcall (pcase-lambda (a `(,b ,c)) c) 1 '(2 3)) ; => 3 (yes, quoting)
  
  ;; Let’s try again not to match:
  (funcall (pcase-lambda (`(1 ,b 3)) a) 2)
  ;; => let*: Wrong type argument: listp, 2
  ;; Cool! *Now* it gives errors!

  (funcall (pcase-lambda (`(1 ,b 3)) b) '(2))
  ;; => nil (okay… I don’t understand)

  (funcall (pcase-lambda (`(1 ,b 3)) b) '(1 2 4))
  ;; => 2 (mmmh… seems lazy on arity and eq-uality… but picky on types)

  (mapcar (pcase-lambda (`(1 (2 ,c 4 . 5))) c)
    '((1 (2 3 4 . 5))
      (1 (2))
      (1 (2 3 4 5 6 . 7))))
  ;; => (2 nil) (indeed, it doesn’t give a fuck about arity)

  (funcall (pcase-lambda (`(1 (2 ,c 4 . 5))) c) '(1 (2 . 3)))
  ;; => let*: Wrong type argument: listp, 3
  ;; But it want sequences to be the right type
  
  (funcall (pcase-lambda (`(1 (2 ,c 4 . 5))) c) '(1 (2 3 . 4)))
  ;; => let*: Wrong type argument: listp, 4
  ;; Even when it doesn’t need to, apparently, so it’s not even laziness

  (funcall (pcase-lambda (`(1 (2 ,c (4 5)))) c) '(1 (2 3))) ; => 3
  (funcall (pcase-lambda (`(1 (2 ,c (4 5)))) c) '(1 (2 3 [4 5])))
  ;; => let*: Wrong type argument: listp, [4 5]
  ;; Yeah, sequences types, only that (it should have used “elt”, so it
  ;; would have always worked (you then only need to have the same
  ;; sequences tree structure)
  
  ;; let’s try out how that acts on arglist features
  (funcall (pcase-lambda (a `(&rest ,rest)) rest) 1 2 3)
  ;; => Wrong number of arguments: (lambda (a arg0) (let* ((x (car
  ;;    arg0)) (x (cdr arg0)) (x (car x)) (x (cdr x))) (let ((rest x))
  ;;    rest))), 3
  
  ;; Ah indeed, to do that it would be:
  (funcall (pcase-lambda (a &rest rest) rest) 1 2 3) ; => (2 3)
  ;; yes &rest works
  
  ;; So, I want, with correct args:
  (funcall (pcase-lambda (a `(&rest ,rest)) rest) 1 '(2 3)) ; => 3
  ;; UH???
  
  (funcall (pcase-lambda (a `(,b ,c &rest ,rest)) rest) 1 '(2 3 4 5 6 7))
  ;; => 5 (okay something strange is going)
  
  (funcall (pcase-lambda (a `(,b ,c &optional ,opt)) opt) 1 '(2 3 4))
  (funcall (pcase-lambda (a `(,b ,c &optional ,opt)) opt) 1 '(2 3))
  ;; => nil => nil (what?)
  
  (funcall (pcase-lambda (a `(,b ,c &optional ,opt)) opt) 1 '(2 3 4 5))
  ;; => 5 (there’s one argument too much)
  
  ;; Don’t say me &optional is matched as an argument?
  (funcall (pcase-lambda (a `(,b ,c &optional ,opt)) (list b c opt))
           1 '(2 3 4 5))                           ; => (2 3 5)
  ;; Is seems to >< so in the end it doesn’t work?
  
  (funcall (pcase-lambda (a &optional ,opt) (cons a opt))
           1 '(2 3 4 5))
  ;; => pcase--macroexpand: Unknown , pattern: (\, opt)
  
  (funcall (pcase-lambda (a &optional opt) (cons a opt))
           1 2)                                    ; => (1 . 2)
  (funcall (pcase-lambda (a &optional opt) (cons a opt))
           1)                                      ; => (1)
  
  ;; Isn’t behavior not supposed to change depending on nesting?
  
  (apply (pcase-lambda (a (b (c))) (cons a c)) '(a (b (c))))
  ;; => pcase--macroexpand: Unknown b pattern: (b (c))
  
  ;; It seems it becomes real `pcase' only from second level:
  (apply (pcase-lambda (a `(,b (,c))) (cons a c)) '(a (b (c))))
  ;; => (a . c)
  
  ;; So first level is what normal lambda (or destructuring-bind) does,
  ;; that looks like real pattern matching that look like the matched
  ;; data, like in ocaml, haskell, etc. and second level is pcase syntax…
#+END_SRC



reply via email to

[Prev in Thread] Current Thread [Next in Thread]