Discussion:
pcase-if-let?
(too old to reply)
Michael Heerdegen
2018-03-28 23:20:50 UTC
Permalink
Hello,

should we add a `pcase-if-let' to pcase.el? While the name might sound
a bit obscure, in my experience I quite often wanted something like
this, instead of having to write

#+begin_src emacs-lisp
(pcase value
(pattern code
more code
...)
(_ other code
more other code
...))
#+end_src

for example.

Here is the result of a first naive implementation trial (which works
for me):

#+begin_src emacs-lisp
(defmacro pcase-if-let (bindings then &rest elses)
"Bind variables according to BINDINGS and eval THEN or ELSE.
This is like `if-let' but BINDINGS is a list of elements of the
form \(PATTERN VALUE-FORM) and it's tested whether all the
PATTERNs match instead of whether VALUE-FORMS are all non-nil."
(declare (indent 2)
(debug ((&rest (pcase-PAT &optional form))
form body)))
(if (null bindings)
'(let* () then)
(let ((sym (make-symbol "sym"))
(sucess-syms '())
(last-sucess-sym nil))
(dotimes (i (length bindings))
(push (make-symbol (format "binding-%d-sucess" i)) sucess-syms))
(cl-callf nreverse sucess-syms)
`(progn
(mapc (lambda (,sym) (setq ,sym nil)) ',sucess-syms)
(pcase nil
((and ,@(mapcar (pcase-lambda (`(,pattern ,value))
`(let (and ,@(if last-sucess-sym `((guard ,last-sucess-sym)) '())
,pattern
(let ,(setq last-sucess-sym (pop sucess-syms)) t))
,value))
bindings))
(if ,last-sucess-sym ,then ,@elses)))))))
#+end_src

Note that there is no need for a `pcase-if' (just don't bind variables
in the patterns), so we could also use this name I think. Also note
that we can't unite this macro with `if-let' (because it is about
non-nil-ness of values, while `pcase-if-let' is about pattern matching,
so a binding like (SYMBOL VALUE) has different semantics).


Thanks,

Michael.
Davis Herring
2018-03-28 23:54:16 UTC
Permalink
Post by Michael Heerdegen
'(let* () then)
This is obviously a typo for

`(let* () ,then)

but why not use

then

?

Davis
--
This product is sold by volume, not by mass. If it appears too dense or
too sparse, it is because mass-energy conversion has occurred during
shipping.
Michael Heerdegen
2018-03-29 00:21:44 UTC
Permalink
Post by Davis Herring
Post by Michael Heerdegen
'(let* () then)
This is obviously a typo for
`(let* () ,then)
Yes, thanks.
Post by Davis Herring
but why not use
then
Took it from the implementation of `if-let'. AFAICT it can make indeed
make a difference in some situations. Doesn't the empty `let*' create a
new lexical environment? Then the scope of a `defvar' without value is
different, for example.


Michael.
Michael Heerdegen
2018-03-29 03:46:49 UTC
Permalink
Doesn't the empty `let*' create a new lexical environment?
Hmm, no, we thought that it should, but I think it doesn't, so I've
removed that `let*' wrapper for now. And fixed some embarrassing
mistakes. New version (named `pcase-if' this time):

#+begin_src emacs-lisp
(defmacro pcase-if (clauses then-form &rest else-forms)
"Eval THEN-FORM or the ELSE-FORMS depending on CLAUSES.
CLAUSES is a list of the form \((PATTERN VALUE-FORM) ...)
Successively try to match every `pcase' PATTERN against its
VALUE-FORM. When all match, eval THEN-FORM, else the
ELSE-FORMS."
(declare (indent 2)
(debug ((&rest (pcase-PAT &optional form))
form body)))
(if (null clauses)
then-form
(let ((success-syms '()) (last-success-sym nil))
(dotimes (i (length clauses))
(push (make-symbol (format "matching-%d-success" i)) success-syms))
(cl-callf nreverse success-syms)
`(let ,(mapcar (lambda (s) `(,s nil)) success-syms)
(pcase nil
((and ,@(mapcar (pcase-lambda (`(,pattern ,value))
`(let (and ,@(and last-success-sym `((guard ,last-success-sym)))
,pattern
(let ,(setq last-success-sym (pop success-syms)) t))
,value))
clauses))
(if ,last-success-sym ,then-form ,@else-forms)))))))
#+end_src

(The constructed pcase form is a bit unorthodox, but it was the simplest
thing that occurred to me.)

Michael.
Drew Adams
2018-03-29 04:14:01 UTC
Permalink
Post by Michael Heerdegen
#+begin_src emacs-lisp
(defmacro pcase-if (clauses then-form &rest else-forms)
"Eval THEN-FORM or the ELSE-FORMS depending on CLAUSES.
CLAUSES is a list of the form \((PATTERN VALUE-FORM) ...)
We usually try to describe the args in order (first CLAUSES
then THEN-FORM, then ELSE-FORMS. So, for example:

Depending on CLAUSES, evaluate THEN-FORM or ELSE-FORMS.
Post by Michael Heerdegen
Successively try to match every `pcase' PATTERN against
its VALUE-FORM. When all match, eval THEN-FORM, else the
ELSE-FORMS."
About the name "pcase-if"...

If that's what this does then why does the name instead
tell us "case" (suggesting a selection, e.g. one of
several cases)?

The name might be better off telling us that this is
an `if-all-match' (or even just `if-all'): If ALL of
the clauses match then do THEN-FORM, else do ELSE-FORMS.

It's the "all" and the "match" that are important,
together with the fact that this is an if-then-else.
It's not really about "case" at all (except in the
sense that there are two cases: THEN and ELSE). In
Lisp, a `case' construct, like `cond', has always
been about choosing among multiple cases, not just
a single test with two case outcomes.

And why the asymmetry between THEN being singular
and ELSE being plural? Emacs-Lisp `if' has such an
asymmetry (for a couple of reasons, which aren't
particularly relevant here), but why should this
`pcase-if' construct be asymmetric? Why not make it
symmetric, like `cond' (and `case'), since it's
already a complex, multipurpose critter?

I'm guessing that all of this `pcase-*' stuff has
taken its names from `pcase'. But that just took
its name, AFAIK, from `case' and "pattern".

This naming is no longer relevant, is it? There's
no set of "cases" involved. Is there really a big
benefit in naming everything that uses a "pcase-style"
pattern "pcase-<SOMETHING>"? Presumably you want to
use similar names to convey the fact that they all
do pattern-matching.

But if what these things have in common is the
_pattern_ (and they all share the same kinds of
pattern-matching, for the most part), then why
emphasize "case" in the name? And why make people
guess that the "p" stands for "pattern" and is what
these are all about?

Not that I really care much - don't get me wrong.
I just don't find such names very helpful. They
don't really say what they're about (not very
discoverable).
Michael Heerdegen
2018-03-29 04:39:28 UTC
Permalink
Post by Drew Adams
We usually try to describe the args in order (first CLAUSES
Depending on CLAUSES, evaluate THEN-FORM or ELSE-FORMS.
Thanks, fixed.
Post by Drew Adams
And why the asymmetry between THEN being singular
and ELSE being plural? Emacs-Lisp `if' has such an
asymmetry (for a couple of reasons, which aren't
particularly relevant here), but why should this
`pcase-if' construct be asymmetric? Why not make it
symmetric, like `cond' (and `case'), since it's
already a complex, multipurpose critter?
I think if we don't make the syntax similar to if, while the semantics
is, people would become crazy. If you want something more like `case',
well, use `pcase' ;-)
Post by Drew Adams
This naming is no longer relevant, is it? There's
no set of "cases" involved. Is there really a big
benefit in naming everything that uses a "pcase-style"
pattern "pcase-<SOMETHING>"? Presumably you want to
use similar names to convey the fact that they all
do pattern-matching.
Yes, your are right with everything, the names are nonsense. I guess we
are still in some kind of intermediate state - AFAIK Stefan hopes to
integrate the pcase stuff more into Emacs innards some day. Dealing
with these wide problems is not what I want to do here. For now, I just
want to be consistent with the existing naming scheme.


Regards,

Michael.


P.S.: Here is the updated definition:

#+begin_src emacs-lisp
(defmacro pcase-if (clauses then &rest elses)
"Depending on CLAUSES, evaluate THEN or ELSES.
CLAUSES is a list of the form \((PATTERN VALUE-FORM) ...)
Successively try to match every `pcase' PATTERN against its
VALUE-FORM. When all match, eval THEN, else the
ELSES."
(declare (indent 2)
(debug ((&rest (pcase-PAT &optional form))
form body)))
(if (null clauses)
then
(let ((success-syms '()) (last-success-sym nil))
(dotimes (i (length clauses))
(push (make-symbol (format "matching-%d-success" i))
success-syms))
(cl-callf nreverse success-syms)
`(let ,(mapcar (lambda (s) `(,s nil)) success-syms)
(pcase nil
((and ,@(mapcar
(pcase-lambda (`(,pattern ,value))
`(let (and ,@(and last-success-sym
`((guard ,last-success-sym)))
,pattern
(let ,(setq last-success-sym
(pop success-syms))
t))
,value))
clauses))
(if ,last-success-sym ,then ,@elses)))))))
#+end_src
Drew Adams
2018-03-29 04:49:16 UTC
Permalink
Post by Michael Heerdegen
Yes, your are right with everything, the names are nonsense. I guess we
are still in some kind of intermediate state - AFAIK Stefan hopes to
integrate the pcase stuff more into Emacs innards some day. Dealing
with these wide problems is not what I want to do here. For now, I just
want to be consistent with the existing naming scheme.
I understand. And I really didn't expect name-changes at
this point. Just wanted to mention that the names of these
`pcase-*' things are not so helpful. It would have been
better to start off with better thinking about the names,
but we're past that now, it seems.
Stefan Monnier
2018-03-29 04:53:29 UTC
Permalink
Post by Michael Heerdegen
Yes, your are right with everything, the names are nonsense. I guess we
are still in some kind of intermediate state - AFAIK Stefan hopes to
integrate the pcase stuff more into Emacs innards some day. Dealing
with these wide problems is not what I want to do here. For now, I just
want to be consistent with the existing naming scheme.
FWIW, I'm a bit worried about this case: it's not like this pcase-if is
a "pcase version of some existing construct", so if we ever want to get
rid of the "pcase-" prefix on it, we'll have to invent a new name
for it.

IOW it's different from pcase-let and pcase-dolist which are
designed to be "replacements" for let/dolist.


Stefan


PS: Regarding the "unorthodox" shape of the pcase construct, you could
generate an expression of the form (pcase (list E1 E2 ...) ((list P1 P2
...) THEN) (_ ELSE)). It would be a good opportunity to try and fix
pcase so that it generates efficient code for such uses (i.e. it
shouldn't build the list only to then check it). There are already uses
of pcase that would benefit from it, typically for smie-rules-function
where we often do (pcase (cons kind token) ...).
Michael Heerdegen
2018-03-29 05:24:59 UTC
Permalink
Post by Stefan Monnier
FWIW, I'm a bit worried about this case: it's not like this pcase-if
is a "pcase version of some existing construct", so if we ever want to
get rid of the "pcase-" prefix on it, we'll have to invent a new name
for it.
True. But "pif" sounds too funny...we need to find a different name.
Post by Stefan Monnier
PS: Regarding the "unorthodox" shape of the pcase construct, you could
generate an expression of the form (pcase (list E1 E2 ...) ((list P1 P2
...) THEN) (_ ELSE)).
What's `list' - a new pattern type? If you write it like this, it looks
like parallel matching of the Pi patterns, though we need to match in
order for i=1,...


Michael.
Stefan Monnier
2018-03-29 11:59:19 UTC
Permalink
Post by Michael Heerdegen
Post by Stefan Monnier
PS: Regarding the "unorthodox" shape of the pcase construct, you could
generate an expression of the form (pcase (list E1 E2 ...) ((list P1 P2
...) THEN) (_ ELSE)).
What's `list' - a new pattern type? If you write it like this, it looks
like parallel matching of the Pi patterns, though we need to match in
order for i=1,...
Oh, right we need the `let*` kind of scoping.


Stefan
Michael Heerdegen
2018-03-30 01:52:47 UTC
Permalink
Post by Stefan Monnier
Oh, right we need the `let*` kind of scoping.
I call it `if-matching' for now (like in "if [given clauses are]
matching [do this else that]").

Apropos "let*": I realized it's semantically absolutely the same as
`pcase-let*' but executing an else branch when not all patterns match
(what pcase-let* silently assumes). So I guess I should rather look how
these are implemented.


Michael.
Drew Adams
2018-03-30 04:07:24 UTC
Permalink
Post by Michael Heerdegen
I call it `if-matching' for now (like in "if [given clauses are]
matching [do this else that]").
IIUC (?), what distinguishes this from other pcase* stuff
is that this one is about matching ALL of a set of clauses.
It's not that this one is about matching and the others
are not. (And this one is a form of "if".)

That's why I suggested something like:

`if-all-match' (or even just `if-all')

Am I wrong that what is important here are "if" and "all",
not "matching"?
Michael Heerdegen
2018-03-30 05:09:15 UTC
Permalink
Post by Drew Adams
Post by Michael Heerdegen
I call it `if-matching' for now (like in "if [given clauses are]
matching [do this else that]").
IIUC (?), what distinguishes this from other pcase* stuff
is that this one is about matching ALL of a set of clauses.
An important difference is also that it is not the canonical `pcase'
counterpart of something existing (as Stefan mentioned), which is why I
dropped the "pcase" suffix.
Post by Drew Adams
It's not that this one is about matching and the others
are not. (And this one is a form of "if".)
`if-all-match' (or even just `if-all')
Am I wrong that what is important here are "if" and "all",
not "matching"?
As we want to drop the "pcase" suffix in this case, shouldn't the name
tell that this is about matching? Else, "all" is IMHO meaningless.
OTOH adding "all" to the name would be ok for me, since it's not obvious
how the clauses are logically combined.


Michael.
Drew Adams
2018-03-30 15:07:17 UTC
Permalink
Post by Michael Heerdegen
Post by Drew Adams
It's not that this one is about matching and the others
are not. (And this one is a form of "if".)
`if-all-match' (or even just `if-all')
Am I wrong that what is important here are "if" and "all",
not "matching"?
(I meant as compared with the others, as mentioned just above.)
Post by Michael Heerdegen
As we want to drop the "pcase" suffix in this case, shouldn't the name
tell that this is about matching? Else, "all" is IMHO meaningless.
OTOH adding "all" to the name would be ok for me, since it's not obvious
how the clauses are logically combined.
Yes. Although even a name prefixed with "pcase" also says
nothing about matching.

`if-all-match' is what I suggested and would still suggest.
But there are no doubt other good possibilities.
Michael Heerdegen
2018-03-30 23:22:27 UTC
Permalink
Post by Drew Adams
`if-all-match' is what I suggested and would still suggest.
Ok, I think I'll use that name.


Thanks,

Michael.
Nathan Moreau
2018-04-17 20:26:41 UTC
Permalink
Post by Michael Heerdegen
#+begin_src emacs-lisp
(defmacro pcase-if (clauses then &rest elses)
"Depending on CLAUSES, evaluate THEN or ELSES.
CLAUSES is a list of the form \((PATTERN VALUE-FORM) ...)
Successively try to match every `pcase' PATTERN against its
VALUE-FORM. When all match, eval THEN, else the
ELSES."
(declare (indent 2)
(debug ((&rest (pcase-PAT &optional form))
form body)))
(if (null clauses)
then
(let ((success-syms '()) (last-success-sym nil))
(dotimes (i (length clauses))
(push (make-symbol (format "matching-%d-success" i))
success-syms))
(cl-callf nreverse success-syms)
`(let ,(mapcar (lambda (s) `(,s nil)) success-syms)
(pcase nil
(pcase-lambda (`(,pattern ,value))
`((guard ,last-success-sym)))
,pattern
(let ,(setq last-success-sym
(pop success-syms))
t))
,value))
clauses))
#+end_src
Hi,

about that new definition, is the following semantics what you expect?


(pcase-if
((`(,q . ,r) '())
(`(,s . ,u) '(b c)))
(list q r s u 'ok)
'nope)
=> nil


?
I would have expected 'nope instead.

Nathan
Michael Heerdegen
2018-04-17 21:04:56 UTC
Permalink
Post by Nathan Moreau
(pcase-if
((`(,q . ,r) '())
(`(,s . ,u) '(b c)))
(list q r s u 'ok)
'nope)
=> nil
?
I would have expected 'nope instead.
Sure, good catch. There's a catchall rule missing in my implementation
that evaluates the else forms when matching fails (like above).

A better implementation would probably more look like that of
`pcase-let*'. Seems I already get something like pcase-if when I change
the pcase--dontcare rule in `pcase--let*'.


Thanks,

Michael.

Loading...