unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* pcase defuns
@ 2021-12-19  4:53 Andrew Hyatt
  2021-12-19  8:34 ` Tassilo Horn
                   ` (4 more replies)
  0 siblings, 5 replies; 25+ messages in thread
From: Andrew Hyatt @ 2021-12-19  4:53 UTC (permalink / raw)
  To: emacs-devel


Hi all,

As a part of a personal project, I wrote a way to define functions 
in an equivalent way to pcases.  For example:

(pcase-defun mytest (a b _)
  "Match on 'a 'b with the third argument a wildcard"
  "a b match")

(pcase-defun mytest (c ,var _)
  "Match on 'c binding VAR, with the third argument a wildcard"
  (format "c %s match" var) )

(mytest 'a 'b 'c) -> "a b match"
(mytest 'c 100 'c) -> "c 100 match"

This is all accomplished by a few small but tricky macros and a 
hashtable that holds all the rules.

I find this method of programming quite useful for certain kinds 
of problems, and pcase is close to what I want, but using pcase 
forced me to encapsulate all of my pattern matching logic in one 
function. Being able to have it spread out in pattern matching 
functions seems much nicer to me.

If this is of interest, I can either add this to the pcase module 
itself, create a package in gnu elpa, or (if there's some reason 
that it shouldn't be done) just keep it in a personal repository.

I'll wait for some guidance before creating a final version, with 
tests, wherever it should go.



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2021-12-19  4:53 pcase defuns Andrew Hyatt
@ 2021-12-19  8:34 ` Tassilo Horn
  2021-12-19 15:33   ` Andrew Hyatt
  2021-12-22 14:07   ` LdBeth
  2021-12-19 17:23 ` Stefan Monnier
                   ` (3 subsequent siblings)
  4 siblings, 2 replies; 25+ messages in thread
From: Tassilo Horn @ 2021-12-19  8:34 UTC (permalink / raw)
  To: Andrew Hyatt; +Cc: emacs-devel

Andrew Hyatt <ahyatt@gmail.com> writes:

Hi Andrew,

> As a part of a personal project, I wrote a way to define functions in
> an equivalent way to pcases.  For example:
>
> (pcase-defun mytest (a b _)
>  "Match on 'a 'b with the third argument a wildcard"
>  "a b match")
>
> (pcase-defun mytest (c ,var _)
>  "Match on 'c binding VAR, with the third argument a wildcard"
>  (format "c %s match" var) )
>
> (mytest 'a 'b 'c) -> "a b match"
> (mytest 'c 100 'c) -> "c 100 match"

So that's basically similar to cl-defgeneric / cl-defmethod just with
pcase pattern matching, right?

In that case, I think I like the cl approach better, i.e., having a
separate definition for the signature + several implementations for
variants.

But since the order of patterns is very significant, I'm not sure if
it's a good idea to specify them separately.  E.g., what if there are
pcase-defun's for mytest in several files?  Then you might get different
behavior depending on the order how files are loaded.  And even in the
"all patterns in mytest.el" case: what if I re-evaluate some
pcase-defuns after the file has been loaded?

So probably I'd favor a pcase-defun which specifies all patterns and
bodies in a single definition, e.g., like:

(pase-defun mytest
  "Docstring"
 ((a b _)
  "a b match")
 ((c ,var _)
  (format "c %s match" var)))

Bye,
Tassilo



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2021-12-19  8:34 ` Tassilo Horn
@ 2021-12-19 15:33   ` Andrew Hyatt
  2021-12-19 17:16     ` Tassilo Horn
  2021-12-22 14:07   ` LdBeth
  1 sibling, 1 reply; 25+ messages in thread
From: Andrew Hyatt @ 2021-12-19 15:33 UTC (permalink / raw)
  To: Tassilo Horn; +Cc: emacs-devel

On Sun, Dec 19, 2021 at 09:34 AM Tassilo Horn <tsdh@gnu.org> 
wrote: 

> Andrew Hyatt <ahyatt@gmail.com> writes: 
> 
> Hi Andrew, 
> 
>> As a part of a personal project, I wrote a way to define 
>> functions in an equivalent way to pcases.  For example: 
>> 
>> (pcase-defun mytest (a b _) 
>>  "Match on 'a 'b with the third argument a wildcard" "a b 
>>  match") 
>> 
>> (pcase-defun mytest (c ,var _) 
>>  "Match on 'c binding VAR, with the third argument a wildcard" 
>>  (format "c %s match" var) ) 
>> 
>> (mytest 'a 'b 'c) -> "a b match" (mytest 'c 100 'c) -> "c 100 
>> match" 
> 
> So that's basically similar to cl-defgeneric / cl-defmethod just 
> with pcase pattern matching, right? 
> 
> In that case, I think I like the cl approach better, i.e., 
> having a separate definition for the signature + several 
> implementations for variants. 
> 
> But since the order of patterns is very significant, I'm not 
> sure if it's a good idea to specify them separately.  E.g., what 
> if there are pcase-defun's for mytest in several files?  Then 
> you might get different behavior depending on the order how 
> files are loaded.  And even in the "all patterns in mytest.el" 
> case: what if I re-evaluate some pcase-defuns after the file has 
> been loaded?

I have a solution for this: a crude one: I just sort all the rules 
according to their flattened length. So the most complicated ones 
should get tried first, which I think is something that can 
communicated to the user. There's other ways the system could 
behave, such as offering to the user to define a priority for the 
rule. But definitely it's easy to make sure new rules can be added 
without crazy things happening.

You've hit upon the issue with these types of systems. Powerful, 
easy to use, but dispatch rules are always vague.  The same thing 
can happen with cl-defmethod, IIRC, where you can have two rules 
that both seem like they should take precedence.  I think offering 
this functionality gives the user that choice - if they want very 
specific ordering, they can just use pcase in a function, either 
in a solution as you propose, or just use pcase itself, which is 
pretty straighforward.

>
> So probably I'd favor a pcase-defun which specifies all patterns and
> bodies in a single definition, e.g., like:
>
> (pase-defun mytest
>   "Docstring"
>  ((a b _)
>   "a b match")
>  ((c ,var _)
>   (format "c %s match" var)))
>
> Bye,
> Tassilo



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2021-12-19 15:33   ` Andrew Hyatt
@ 2021-12-19 17:16     ` Tassilo Horn
  2021-12-19 19:05       ` Stefan Monnier
  0 siblings, 1 reply; 25+ messages in thread
From: Tassilo Horn @ 2021-12-19 17:16 UTC (permalink / raw)
  To: Andrew Hyatt; +Cc: emacs-devel

Andrew Hyatt <ahyatt@gmail.com> writes:

Hi Andrew,

> I have a solution for this: a crude one: I just sort all the rules
> according to their flattened length. So the most complicated ones
> should get tried first, which I think is something that can
> communicated to the user.

Hm, well, in that case (a ,var) is longer than (a b) so the former will
always be taken although a user might have them ordered the other way
round and expect (a ,var) only to match if the second argument is not b.

> You've hit upon the issue with these types of systems.  Powerful, easy
> to use, but dispatch rules are always vague.  The same thing can
> happen with cl-defmethod, IIRC, where you can have two rules that both
> seem like they should take precedence.

Oh dear, my head becomes dizzy when reading their dispatch behavior at
(info "(elisp) Generic Functions").  Well, I mean, the typical uses can
be pretty easy to understand but if you mix and match different kinds of
specifiers, it becomes unwieldy, like what happens when you have two
methods where one specifies the first argument must be an integer and
the other says it applies if the first argument is (eql 42) [where 42 is
obviously an integer]?  I can't read a priority order between different
kinds of specifiers...

Bye,
Tassilo



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2021-12-19  4:53 pcase defuns Andrew Hyatt
  2021-12-19  8:34 ` Tassilo Horn
@ 2021-12-19 17:23 ` Stefan Monnier
  2021-12-19 21:08   ` Andrew Hyatt
  2021-12-20  4:43 ` Richard Stallman
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 25+ messages in thread
From: Stefan Monnier @ 2021-12-19 17:23 UTC (permalink / raw)
  To: Andrew Hyatt; +Cc: emacs-devel

> As a part of a personal project, I wrote a way to define functions in an
> equivalent way to pcases.  For example:
>
> (pcase-defun mytest (a b _)
>  "Match on 'a 'b with the third argument a wildcard"
>  "a b match")
>
> (pcase-defun mytest (c ,var _)
>  "Match on 'c binding VAR, with the third argument a wildcard"
>  (format "c %s match" var) )
>
> (mytest 'a 'b 'c) -> "a b match"
> (mytest 'c 100 'c) -> "c 100 match"
>
> This is all accomplished by a few small but tricky macros and a hashtable
> that holds all the rules.

This kind of design crossed my mind a few times but I couldn't come up
with a way to give it a reasonable semantics and implementation.
Beside the issue of precedence/ordering already mentioned by Tassilo,
there's the issue of scoping and order/timing of macroexpansion.  E.g.:

    (let ((x 0))
      (pcase-defun mytest (inc-x)
        (setq x (1+ x)))
      (pcase-defun mytest (get-x)
        x))
    
    (let ((y 0))
      (pcase-defun mytest (inc-y)
        (setq y (1+ y)))
      (pcase-defun mytest (get-y)
        y))

Does this work "right" with your code?

Or:

    (eval-when-compile (require 'cl-lib))

    (pcase-defun foo (..)
      ..
      (cl-incf ..)
      ..)

is `cl-incf` properly macroexpanded during compilation of the file, or
is it delayed to when the file is loaded, at which point the `cl-incf`
macro may be undefined?


        Stefan




^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2021-12-19 17:16     ` Tassilo Horn
@ 2021-12-19 19:05       ` Stefan Monnier
  2021-12-20  5:56         ` Tassilo Horn
  0 siblings, 1 reply; 25+ messages in thread
From: Stefan Monnier @ 2021-12-19 19:05 UTC (permalink / raw)
  To: Tassilo Horn; +Cc: Andrew Hyatt, emacs-devel

> Oh dear, my head becomes dizzy when reading their dispatch behavior at
> (info "(elisp) Generic Functions").  Well, I mean, the typical uses can
> be pretty easy to understand but if you mix and match different kinds of
> specifiers, it becomes unwieldy, like what happens when you have two
> methods where one specifies the first argument must be an integer and
> the other says it applies if the first argument is (eql 42) [where 42 is
> obviously an integer]?  I can't read a priority order between different
> kinds of specifiers...

When the specializers are "naturally nested" (i.e. if you take them as
sets, one set is a subset of the other), then the desired precedence is
pretty clear and cl-generic *should* obey that order (if it doesn't, it
should be considered as a bug, tho I wouldn't be surprised if someone
comes up with examples which we may decide not to fix).


        Stefan




^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2021-12-19 17:23 ` Stefan Monnier
@ 2021-12-19 21:08   ` Andrew Hyatt
  2021-12-21  4:15     ` Richard Stallman
  2021-12-21 15:32     ` Stefan Monnier
  0 siblings, 2 replies; 25+ messages in thread
From: Andrew Hyatt @ 2021-12-19 21:08 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

On Sun, Dec 19, 2021 at 12:23 PM Stefan Monnier 
<monnier@iro.umontreal.ca> wrote: 

>> As a part of a personal project, I wrote a way to define 
>> functions in an equivalent way to pcases.  For example: 
>> 
>> (pcase-defun mytest (a b _) 
>>  "Match on 'a 'b with the third argument a wildcard" "a b 
>>  match") 
>> 
>> (pcase-defun mytest (c ,var _) 
>>  "Match on 'c binding VAR, with the third argument a wildcard" 
>>  (format "c %s match" var) ) 
>> 
>> (mytest 'a 'b 'c) -> "a b match" (mytest 'c 100 'c) -> "c 100 
>> match" 
>> 
>> This is all accomplished by a few small but tricky macros and a 
>> hashtable that holds all the rules. 
> 
> This kind of design crossed my mind a few times but I couldn't 
> come up with a way to give it a reasonable semantics and 
> implementation.  Beside the issue of precedence/ordering already 
> mentioned by Tassilo, there's the issue of scoping and 
> order/timing of macroexpansion.  E.g.: 
> 
>     (let ((x 0)) 
>       (pcase-defun mytest (inc-x) 
>         (setq x (1+ x))) 
>       (pcase-defun mytest (get-x) 
>         x)) 
>      (let ((y 0)) 
>       (pcase-defun mytest (inc-y) 
>         (setq y (1+ y))) 
>       (pcase-defun mytest (get-y) 
>         y)) 
> 
> Does this work "right" with your code? 
> 
> Or: 
> 
>     (eval-when-compile (require 'cl-lib)) 
> 
>     (pcase-defun foo (..) 
>       ..  (cl-incf ..)  ..) 
> 
> is `cl-incf` properly macroexpanded during compilation of the 
> file, or is it delayed to when the file is loaded, at which 
> point the `cl-incf` macro may be undefined?

Great points, thank you! Indeed, both of these wouldn't work - I'm 
just quoting and storing the function body for later.  I'll have 
to think about whether there is something more clever I can do 
that would fix these problems.

Does every defun-like thing obey these rules?  I'm not familiar 
with the range of them, so I'm not sure if this would be violating 
convention or not.  If so, one thing I can do is to try to 
distance this from defun perhaps with a different name such as 
`pcase-pattern', and making calls go through a function such as 
`pcase-call'.

>
>
>         Stefan



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2021-12-19  4:53 pcase defuns Andrew Hyatt
  2021-12-19  8:34 ` Tassilo Horn
  2021-12-19 17:23 ` Stefan Monnier
@ 2021-12-20  4:43 ` Richard Stallman
  2021-12-23  2:30 ` Po Lu
  2022-03-26 17:41 ` Andrew Hyatt
  4 siblings, 0 replies; 25+ messages in thread
From: Richard Stallman @ 2021-12-20  4:43 UTC (permalink / raw)
  To: Andrew Hyatt; +Cc: emacs-devel

[[[ To any NSA and FBI agents reading my email: please consider    ]]]
[[[ whether defending the US Constitution against all enemies,     ]]]
[[[ foreign or domestic, requires you to follow Snowden's example. ]]]

I see a substantive drawback with this way of defining functions.
Where is "the definition of mytest"?  What place should commands take
you to, when you ask to move to the definition?

What if different cases are defined in different files?  That would be
very confusing.

Another confusing thing is that it doesn't work anything like `pcase'.

There is a benefit to putting all these cases into one form:

(defun mytest (&rest args)
  (CASE-CONSTRUCT args
    (PATTERN ;; Match on 'a 'b with the third argument a wildcard
     "a b match")

    (PATTERN ;; Match on 'c binding VAR, with the third argument a wildcard
     (format "c %s match" var)) ))

That adds two additional lines, and three spaces of indentation,
and it avoids cans of worms.

-- 
Dr Richard Stallman (https://stallman.org)
Chief GNUisance of the GNU Project (https://gnu.org)
Founder, Free Software Foundation (https://fsf.org)
Internet Hall-of-Famer (https://internethalloffame.org)





^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2021-12-19 19:05       ` Stefan Monnier
@ 2021-12-20  5:56         ` Tassilo Horn
  0 siblings, 0 replies; 25+ messages in thread
From: Tassilo Horn @ 2021-12-20  5:56 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Andrew Hyatt, emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

>> Oh dear, my head becomes dizzy when reading their dispatch behavior
>> at (info "(elisp) Generic Functions").  Well, I mean, the typical
>> uses can be pretty easy to understand but if you mix and match
>> different kinds of specifiers, it becomes unwieldy, like what happens
>> when you have two methods where one specifies the first argument must
>> be an integer and the other says it applies if the first argument is
>> (eql 42) [where 42 is obviously an integer]?  I can't read a priority
>> order between different kinds of specifiers...
>
> When the specializers are "naturally nested" (i.e. if you take them as
> sets, one set is a subset of the other), then the desired precedence
> is pretty clear and cl-generic *should* obey that order

A quick test with types such as integer and float and eql picking
specific elements in the sets specified by those types suggest it works
well.

> (if it doesn't, it should be considered as a bug, tho I wouldn't be
> surprised if someone comes up with examples which we may decide not to
> fix).

At least I cannot easily come up with some counter-example.  Maybe usage
of the subr type specializer could result in unexpected results with
native-comp given that every function will become a subr there at some
point.

Bye,
Tassilo



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2021-12-19 21:08   ` Andrew Hyatt
@ 2021-12-21  4:15     ` Richard Stallman
  2021-12-21  5:20       ` Andrew Hyatt
  2021-12-21 15:32     ` Stefan Monnier
  1 sibling, 1 reply; 25+ messages in thread
From: Richard Stallman @ 2021-12-21  4:15 UTC (permalink / raw)
  To: Andrew Hyatt; +Cc: monnier, emacs-devel

[[[ To any NSA and FBI agents reading my email: please consider    ]]]
[[[ whether defending the US Constitution against all enemies,     ]]]
[[[ foreign or domestic, requires you to follow Snowden's example. ]]]

The idea of defining a function in pieces, scattered around one or
more files, is attractive as regards writing them.  But it creates
an inconvenience for subsequent maintenance by other people.

The first thing you're likely to do when you see a call to a function
you don't know about is to find its definition.  For a
piecewise-defined function, "the" definition doesn't exist.  It has a
number of partial definitions, scattered around one file or perhaps
multiple files.

If you think it is helpful for clarity to put the code for handling
various cases in various places in the file, no new construct is
needed for that.  You can define a function for handling each case, and
put its definition in the place you want it.  Then define one overall
function (you can think of it as "generic") which detects the cases
and calls those.

This works with our tools.

Let's contrast this with generic functions.

A generic function has a single central definition
which describes how to call it.  It is easy to find that.

Finding the definitions of all the methods may be difficult.
You may need to grep for them.

Calling the generic function selects a method by data types, and you
can generally tell, for given arguments, what data types they have.
You can probably tell that one method is the right one without seeing
all the others and comparing them.

Thus, generic functions cause one kind of inconvenience for studying a
program, but avoids the other kinds.

Let's not add any constructs that increase the level of complexity of
genericness beyond this.


-- 
Dr Richard Stallman (https://stallman.org)
Chief GNUisance of the GNU Project (https://gnu.org)
Founder, Free Software Foundation (https://fsf.org)
Internet Hall-of-Famer (https://internethalloffame.org)





^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2021-12-21  4:15     ` Richard Stallman
@ 2021-12-21  5:20       ` Andrew Hyatt
  2021-12-22  4:18         ` Richard Stallman
  0 siblings, 1 reply; 25+ messages in thread
From: Andrew Hyatt @ 2021-12-21  5:20 UTC (permalink / raw)
  To: rms; +Cc: monnier, emacs-devel

On Mon, Dec 20, 2021 at 11:15 PM Richard Stallman <rms@gnu.org> 
wrote: 

> [[[ To any NSA and FBI agents reading my email: please consider 
> ]]] [[[ whether defending the US Constitution against all 
> enemies,     ]]] [[[ foreign or domestic, requires you to follow 
> Snowden's example. ]]] 
> 
> The idea of defining a function in pieces, scattered around one 
> or more files, is attractive as regards writing them.  But it 
> creates an inconvenience for subsequent maintenance by other 
> people. 
> 
> The first thing you're likely to do when you see a call to a 
> function you don't know about is to find its definition.  For a 
> piecewise-defined function, "the" definition doesn't exist.  It 
> has a number of partial definitions, scattered around one file 
> or perhaps multiple files. 
> 
> If you think it is helpful for clarity to put the code for 
> handling various cases in various places in the file, no new 
> construct is needed for that.  You can define a function for 
> handling each case, and put its definition in the place you want 
> it.  Then define one overall function (you can think of it as 
> "generic") which detects the cases and calls those. 
> 
> This works with our tools. 
> 
> Let's contrast this with generic functions. 
> 
> A generic function has a single central definition which 
> describes how to call it.  It is easy to find that. 
> 
> Finding the definitions of all the methods may be difficult. 
> You may need to grep for them. 
> 
> Calling the generic function selects a method by data types, and 
> you can generally tell, for given arguments, what data types 
> they have.  You can probably tell that one method is the right 
> one without seeing all the others and comparing them. 
> 
> Thus, generic functions cause one kind of inconvenience for 
> studying a program, but avoids the other kinds. 
> 
> Let's not add any constructs that increase the level of 
> complexity of genericness beyond this. 

These are all good points, thanks.

I agree that just implementing everything in a function is 
reasonable.  For that, just using pcase seems good enough, 
although it might be interesting to have a very different way of 
writing a function such as

(pcase-defun mytest
  "Demonstrates a way of writing defuns via pcase matching."
  ((a b _) "a b match")
  (`(c ,v _) (format "c %s match" v)))

Not sure if I like this syntax, it does seem to lend itself to the 
idea of a pcase-centric way of programming, but it's also a bit 
odd, and doesn't seem to buy much. However, it does at least 
answer the objections raised to my initial proposal. Of course, so 
does not doing anything.

>
>
> -- 
> Dr Richard Stallman (https://stallman.org)
> Chief GNUisance of the GNU Project (https://gnu.org)
> Founder, Free Software Foundation (https://fsf.org)
> Internet Hall-of-Famer (https://internethalloffame.org)



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2021-12-19 21:08   ` Andrew Hyatt
  2021-12-21  4:15     ` Richard Stallman
@ 2021-12-21 15:32     ` Stefan Monnier
  1 sibling, 0 replies; 25+ messages in thread
From: Stefan Monnier @ 2021-12-21 15:32 UTC (permalink / raw)
  To: Andrew Hyatt; +Cc: emacs-devel

> Great points, thank you! Indeed, both of these wouldn't work - I'm just
> quoting and storing the function body for later.  I'll have to think about
> whether there is something more clever I can do that would fix
> these problems.

You could put the bodies into auxiliary functions (which then get
compiled in the usual way) and only quote&store the "pattern +
auxiliary-function-name".  That should get you much cleaner semantics
already (might be sufficient for the examples I showed).  It still would
have odd corner cases when you have code in the patterns or when the
patterns use pcase patterns defined in a file loaded via
`eval-when-compile` (the latter can be handled by pre-expanding the
patterns via `pcase--macroexpand` before you quote&store).

> Does every defun-like thing obey these rules?

That's not really the question.  The question is rather what should your
code do in those cases, and how do you document it.

BTW, regarding the problem of ordering, an easy way out is to pass the
ball to the programmer: use a syntax like (pcase-defun NAME PRIO (ARGS) BODY)
where PRIO is a number giving the "priority" of the rule; then you just
order the rules by priority.

That's what I did in `cl-generic-generalizers`, in `add-function`, and
in `add-hook` ;-)


        Stefan




^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2021-12-21  5:20       ` Andrew Hyatt
@ 2021-12-22  4:18         ` Richard Stallman
  2021-12-23  1:52           ` Andrew Hyatt
  0 siblings, 1 reply; 25+ messages in thread
From: Richard Stallman @ 2021-12-22  4:18 UTC (permalink / raw)
  To: Andrew Hyatt; +Cc: monnier, emacs-devel

[[[ To any NSA and FBI agents reading my email: please consider    ]]]
[[[ whether defending the US Constitution against all enemies,     ]]]
[[[ foreign or domestic, requires you to follow Snowden's example. ]]]

  > I agree that just implementing everything in a function is 
  > reasonable.  For that, just using pcase seems good enough, 
  > although it might be interesting to have a very different way of 
  > writing a function such as

  > (pcase-defun mytest
  >   "Demonstrates a way of writing defuns via pcase matching."
  >   ((a b _) "a b match")
  >   (`(c ,v _) (format "c %s match" v)))

This avoids the split-up-definition problems I was talking about,
since it is all in one place with one name.

Please make the defining form's name follow th convention of starting
with `def', so people and tools will recognize that it is a defining
form.

Also, why use `pcase' in the name?  According to the docs of `pcase',
this pattern matching is not similar.


-- 
Dr Richard Stallman (https://stallman.org)
Chief GNUisance of the GNU Project (https://gnu.org)
Founder, Free Software Foundation (https://fsf.org)
Internet Hall-of-Famer (https://internethalloffame.org)





^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2021-12-19  8:34 ` Tassilo Horn
  2021-12-19 15:33   ` Andrew Hyatt
@ 2021-12-22 14:07   ` LdBeth
  1 sibling, 0 replies; 25+ messages in thread
From: LdBeth @ 2021-12-22 14:07 UTC (permalink / raw)
  To: Andrew Hyatt; +Cc: emacs-devel, Tassilo Horn

[-- Attachment #1: Type: text/plain, Size: 887 bytes --]


>>>>> In <871r299e5j.fsf@gnu.org> 
>>>>>	Tassilo Horn <tsdh@gnu.org> wrote:
Tassilo> Andrew Hyatt <ahyatt@gmail.com> writes:

Tassilo> Hi Andrew,

> As a part of a personal project, I wrote a way to define functions in
> an equivalent way to pcases.  For example:
>
> (pcase-defun mytest (a b _)
>  "Match on 'a 'b with the third argument a wildcard"
>  "a b match")
>
> (pcase-defun mytest (c ,var _)
>  "Match on 'c binding VAR, with the third argument a wildcard"
>  (format "c %s match" var) )
>
> (mytest 'a 'b 'c) -> "a b match"
> (mytest 'c 100 'c) -> "c 100 match"

Tassilo> So that's basically similar to cl-defgeneric / cl-defmethod just with
Tassilo> pcase pattern matching, right?


That reminds me of an old tiny piece of Common Lisp code from CMU AI
Repository that implements single inheritance message passing OOP,
which could be easily ported to Emacs Lisp.

-- 
LDB



[-- Attachment #2: boops.lisp --]
[-- Type: text/plain, Size: 11385 bytes --]

;;; -*-Mode: Lisp; Syntax: Common-lisp; Package: BOOPS -*-

;;; Copyright (c) 1992,1991,1990,1989,1988 Koenraad De Smedt

;;;   Koenraad De Smedt
;;;   Unit of Experimental and Theoretical Psychology 
;;;   Leiden University
;;;   P.O. Box 9555
;;;   2300 RB  Leiden
;;;   The Netherlands
;;;   E-mail: desmedt@rulfsw.leidenuniv.nl

;;; BOOPS (Beginner's Object Oriented Programming System) is an
;;; applicative object-oriented programming system implemented as an
;;; extension of Common LISP. It is a scaled-down version of ORBIT
;;; (Steels 1983) and CommonORBIT (De Smedt 1987) modified under the
;;; influence of OOPS (Luger & Stubblefield 1989, "AI and the design
;;; of expert systems", Chapter 14).

;;; BOOPS is distributed in the hope that it will be useful, but
;;; without any warranty. No author or distributor accepts
;;; responsibility to anyone for the consequences of using it or for
;;; whether it serves any particular purpose or works at all, unless
;;; he says so in writing.

;;; Copyright Release Statement:

;;; Everyone is granted permission to copy, modify and redistribute
;;; BOOPS but only under the conditions that (1) distribution is free
;;; and without cost, (2) any modifications are also sent to the above
;;; address, and (3) this entire notice is preserved on all copies.

(defpackage "BOOPS"
  (:use "COMMON-LISP")
  (:export
   "A"
   "AN"
   "DEFASPECT"
   "DEFOBJECT"
   "EEN"
   "ISA"
   "ISA?"
   "MESSAGE"
   "OBJECT"
   "SET-VALUE"
   "SHOW"
   "TRACE-MESSAGE"
   "UNDEFINED"
   "UNTRACE-MESSAGE"
   ))

(in-package "BOOPS")

;;; ----- Print herald -----

(COND (*LOAD-VERBOSE*
       (TERPRI)
       (PRINC 
	"BOOPS (c) 1992,1990,1989,1988 Koenraad De Smedt")))

;;; ----- Undefined -----

(DEFCONSTANT UNDEFINED 'UNDEFINED
  "The value returned from an object-oriented function call
   when the aspect is not defined for the object.")

;;; ----- Access to internal components of objects -----

;;; the ISA of an object is another object
;;; only single inheritance is supported

(DEFMACRO OBJECT-ISA (OBJECT)
  "Find isa in an object."
  `(GET ,OBJECT 'ISA))

;;; ASPECTS of an object are a list
;;; each aspect consists of a name and a definition
;;; a definition consists of a type and a filler

(DEFMACRO OBJECT-ASPECTS (OBJECT)
  "Find aspects in an object."
  `(GET ,OBJECT 'ASPECTS))

(DEFMACRO FIND-ASPECT (OBJECT ASPECT-NAME)
  "Find aspect in an object."
  `(ASSOC ,ASPECT-NAME (OBJECT-ASPECTS ,OBJECT)))

(DEFMACRO ASPECT-NAME (ASPECT)
  "Return the name of this aspect."
  `(FIRST ,ASPECT))

(DEFMACRO ASPECT-DEFINITION (ASPECT)
  "Return the definition of this aspect, in terms of type and filler."
  `(REST ,ASPECT))

(DEFMACRO ASPECT-TYPE (ASPECT-DEFINITION)
  "Return the type of this aspect definition."
  `(FIRST ,ASPECT-DEFINITION))

(DEFMACRO ASPECT-FILLER (ASPECT-DEFINITION)
  "Return the filler of this aspect definition."
  `(REST ,ASPECT-DEFINITION))

(DEFMACRO MAKE-ASPECT-DEFINITION (TYPE FILLER)
  "Make aspect definition with given type and filler."
  `(CONS ,TYPE ,FILLER))

(DEFMACRO MAKE-ASPECT (NAME TYPE FILLER)
  "Make aspect with given name, type and filler."
  `(CONS ,NAME (MAKE-ASPECT-DEFINITION ,TYPE ,FILLER)))

;;; ----- Making delegation links -----

(DEFUN ISA (OBJECT ISA)
  "Establish an isa relation. The OBJECT will then by
   default delegate all aspects to the ISA."
  (COND ((OR (EQ OBJECT ISA)
	     (ISA? ISA OBJECT))
	 (WARN "Making ~A inherit from ~A would cause circularity."
	       OBJECT ISA))
	(T (SETF (OBJECT-ISA OBJECT) ISA))))

(DEFUN ISA? (OBJECT ISA)
  "True if OBJECT is indeed a object of ISA."
  (LET ((CURRENT-ISA (OBJECT-ISA OBJECT)))
    (COND ((EQ ISA CURRENT-ISA) T)
	  ((NULL CURRENT-ISA) NIL)
	  (T (ISA? CURRENT-ISA ISA)))))

;;; ----- Adding and removing aspects -----

(DEFUN ADD-ASPECT (OBJECT ASPECT-NAME FILLER TYPE)
  "Add an aspect to an object."
  (LET ((CURRENT-ASPECT (FIND-ASPECT OBJECT ASPECT-NAME)))
    (COND ((NULL CURRENT-ASPECT)
           ;; new aspect
           (SETF (OBJECT-ASPECTS OBJECT)
		 (CONS (MAKE-ASPECT ASPECT-NAME TYPE FILLER)
		       (OBJECT-ASPECTS OBJECT))))
          (T            ;there is already an aspect
            (LET ((CURRENT-DEFINITION (ASPECT-DEFINITION CURRENT-ASPECT)))
             (UNLESS (AND (EQ (ASPECT-TYPE CURRENT-DEFINITION) TYPE)
			  (EQ (ASPECT-FILLER CURRENT-DEFINITION) FILLER))
               ;; if type and filler are eq to those in current aspect,
               ;; do nothing
               ;; else replace the definition
               (SETF (ASPECT-DEFINITION CURRENT-ASPECT)
                     (MAKE-ASPECT-DEFINITION TYPE FILLER)))))))
  ASPECT-NAME)

;;; ----- Defining aspects -----

(DEFMACRO DEFASPECT (OBJECT ASPECT-NAME &REST DEFINITION)
  "Define an aspect. The aspect name and object are not evaluated.
   This macro has the following syntax:
        DEFASPECT aspect object [type] filler
   The aspect definition is associated with the given object.
   If the type is omitted, the default is :VALUE.
   The following keywords for explicit aspect types are possible:
        :VALUE filler
   The filler can be any Lisp object which is simply returned.
        :FUNCTION filler
   or   :FUNCTION ([var ...]) form ...
   The filler is a function which is to be applied. The filler is
   a function with the given lambda list and forms.
        :IF-NEEDED filler
   or   :IF-NEEDED ([var ...]) form ...
   Like :FUNCTION but the result is to be memoized."
  (EXPAND-DEFASPECT ASPECT-NAME `',OBJECT DEFINITION))

(DEFUN EXPAND-DEFASPECT (ASPECT-NAME OBJECT DEFINITION)
  "Expansion for DEFASPECT."
  (COND (DEFINITION			;not an empty definition
	 (EXPAND-ASPECT-DEFINITION
	   `',ASPECT-NAME OBJECT
	   (FIRST DEFINITION) (REST DEFINITION)))))

(DEFUN EXPAND-ASPECT-DEFINITION (ASPECT-NAME OBJECT TYPE FILLER-LIST)
  "Expansion for definition in DEFASPECT."
  (COND
     ((NULL FILLER-LIST)           ;implicit type :VALUE
        `(ADD-ASPECT ,OBJECT ,ASPECT-NAME ,TYPE :VALUE))
     (T                            ;explicit type
      (CASE TYPE
        (:VALUE
          `(ADD-ASPECT
             ,OBJECT ,ASPECT-NAME ,(FIRST FILLER-LIST) ,TYPE))
        ((:FUNCTION :IF-NEEDED)                ;expand both the same
         `(ADD-ASPECT
            ,OBJECT
	    ,ASPECT-NAME
            ,(COND ((NULL (REST FILLER-LIST))     ;just one element?
		    ;; assume it contains a function
		    (FIRST FILLER-LIST))
		   (T
		    ;; assume it contains a variable list and body
		    `#'(LAMBDA ,(FIRST FILLER-LIST)
			 ,@(REST FILLER-LIST))))
            ,TYPE))))))

;;; ----- Defining named objects -----

(DEFMACRO DEFOBJECT (NAME ISA &BODY ASPECTS)
  "Define a named BOOPS object by assigning isas and defining
   aspects. The arguments are not evaluated.
   A symbol is a isa, lists are aspect definitions. Example:
     (DEFOBJECT WOMAN PERSON (SEX 'FEMALE))
   Aspect definitions are processed as by DEFASPECT."
  `(PROGN
     (ISA ',NAME ',ISA)
     (SETF (OBJECT-ASPECTS ',NAME) NIL)
     ,@(EXPAND-ASPECT-DEFINITIONS ASPECTS `',NAME)
     ',NAME))

(DEFUN EXPAND-ASPECT-DEFINITIONS (ASPECT-DEFINITIONS OBJECT)
  "Expand isa and aspect definitions in object definition."
  (MAPCAR #'(LAMBDA (ASPECT-DEFINITION)
	      (EXPAND-DEFASPECT (FIRST ASPECT-DEFINITION)
				OBJECT
				(REST ASPECT-DEFINITION)))
	  ASPECT-DEFINITIONS))

;;; ----- Defining anonymous objects -----

(DEFMACRO A (ISA &REST ASPECTS)
  "Define an anonymous BOOPS object by assigning isas and
   defining aspects. The arguments are not evaluated.
   A symbol is a isa, lists are aspect definitions. Example:
     (A PERSON (SEX 'FEMALE))
   Aspect definitions are processed as by DEFASPECT."
   (LET ((OBJECT (GENSYM)))
     `(LET ((,OBJECT (GENSYM (STRING ',ISA))))
        (ISA ,OBJECT ',ISA)
       ,@(EXPAND-ASPECT-DEFINITIONS ASPECTS OBJECT)
       ,OBJECT)))

(DEFMACRO AN (ISA &REST ASPECTS)
  "Synonym of A."
  `(A ,ISA ,@ASPECTS))

(DEFMACRO EEN (ISA &REST ASPECTS)
  "Synonym of A for Dutch."
  `(A ,ISA ,@ASPECTS))

;;; ----- Message passing -----

(DEFUN MESSAGE (OBJECT ASPECT-NAME &REST ARGS)
  "Message passing. Get the definition of the aspect for the
   object (the first argument) and if it is a function, apply
   that function to all the arguments."
  (COND ((GET ASPECT-NAME 'TRACED)
	 (FORMAT *TRACE-OUTPUT* "~%-> ~A ~A ~A"
		 ASPECT-NAME OBJECT ARGS)))
  (LET ((DEFINITION (GET-DEFINITION ASPECT-NAME OBJECT)))
    (LET ((TYPE (ASPECT-TYPE (FIRST DEFINITION)))
	  (FILLER (ASPECT-FILLER (FIRST DEFINITION)))
	  (SOURCE (SECOND DEFINITION)))
      ;; perform action according to type
      (LET ((RESULT
	      (CASE TYPE
		(:VALUE FILLER)
		(:FUNCTION (APPLY FILLER (CONS OBJECT ARGS)))
		(:IF-NEEDED
		  (SETQ FILLER		;reuse variable filler
			(APPLY FILLER (CONS OBJECT ARGS)))
		  (COND ((AND SOURCE
			      (NOT (EQ FILLER UNDEFINED)))
			 ;; inherited and not undefined, so memoize
			 (ADD-ASPECT OBJECT ASPECT-NAME FILLER :VALUE)))
		  FILLER)
		(OTHERWISE UNDEFINED))))
	(COND ((GET ASPECT-NAME 'TRACED)
	       (FORMAT *TRACE-OUTPUT* "~%<- ~A ~A ~A"
		       ASPECT-NAME OBJECT RESULT)))
	RESULT))))

;;; ----- Retrieving the definition of an aspect for an object -----

(DEFUN GET-DEFINITION (ASPECT-NAME OBJECT)
  "Get the definition of an aspect for an object.
   Return a list of the definition and the object providing it (if found
   AND inherited, otherwise NIL)."
  (LET ((OWN-DEFINITION
	  (ASPECT-DEFINITION (FIND-ASPECT OBJECT ASPECT-NAME))))
    (COND (OWN-DEFINITION
	   (LIST OWN-DEFINITION NIL))
	  (T (GET-DEFINITION-FROM-ISA
	       ASPECT-NAME (OBJECT-ISA OBJECT))))))

(DEFUN GET-DEFINITION-FROM-ISA (ASPECT-NAME OBJECT)
  "Get the definition of an aspect from the isa of an object.
   Return a list of the definition and the object providing it or NIL."
  (COND ((NULL OBJECT) (LIST NIL NIL))
	(T
	 (LET ((ASPECT (FIND-ASPECT OBJECT ASPECT-NAME)))
	   (COND (ASPECT
		  (LIST (ASPECT-DEFINITION ASPECT)
			OBJECT))
		 (T
		  (GET-DEFINITION-FROM-ISA ASPECT-NAME
					   (OBJECT-ISA OBJECT))))))))

;;; ----- Tracing messages -----

(DEFMACRO TRACE-MESSAGE (MESSAGE)
  "Trace a message upon receipt and return of result."
  `(SETF (GET ',MESSAGE 'TRACED) T))

(DEFMACRO UNTRACE-MESSAGE (MESSAGE)
  "Untrace a message."
  `(SETF (GET ',MESSAGE 'TRACED) NIL))

;;; ----- The vanilla object -----

(DEFOBJECT OBJECT
	   NIL
  (SHOW
    :FUNCTION
    #'(LAMBDA (SELF &OPTIONAL (OUTPUT-STREAM *STANDARD-OUTPUT*))
	"Display a description of the object to the output stream."
	(COND ((OBJECT-ISA SELF)
	       (FORMAT OUTPUT-STREAM "~&~S is a ~S"
		       SELF (OBJECT-ISA SELF))))
	(DOLIST (ASPECT (OBJECT-ASPECTS SELF))
	  (LET ((TYPE (ASPECT-TYPE (ASPECT-DEFINITION ASPECT)))
		(FILLER (ASPECT-FILLER (ASPECT-DEFINITION ASPECT))))
	    (FORMAT OUTPUT-STREAM "~&  aspect ~A ~S = ~S"
		    (ASPECT-NAME ASPECT) TYPE FILLER)))
	SELF))
  (SET-VALUE
    :FUNCTION
    #'(LAMBDA (SELF ASPECT-NAME NEW-VALUE)
	"Give the aspect a new value."
	(ADD-ASPECT SELF ASPECT-NAME NEW-VALUE :VALUE)
	(LIST ASPECT-NAME NEW-VALUE)))
  )

;;; possible extensions:
;;; - make objects inherit from vanilla object if not otherwise defined
;;; - make messages generic functions: (friend 'peter)
;;;   (advantage = you can apply, map, trace etc. like normal functions)
;;; - add roles (whose 'friend 'peter)
;;; - do multiple inheritance
;;; - implement DELETE-ASPECT, NOT-ISA, etc.

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2021-12-22  4:18         ` Richard Stallman
@ 2021-12-23  1:52           ` Andrew Hyatt
  2021-12-24  4:13             ` Richard Stallman
  0 siblings, 1 reply; 25+ messages in thread
From: Andrew Hyatt @ 2021-12-23  1:52 UTC (permalink / raw)
  To: rms; +Cc: monnier, emacs-devel

On Tue, Dec 21, 2021 at 11:18 PM Richard Stallman <rms@gnu.org> 
wrote: 

> [[[ To any NSA and FBI agents reading my email: please consider 
> ]]] [[[ whether defending the US Constitution against all 
> enemies,     ]]] [[[ foreign or domestic, requires you to follow 
> Snowden's example. ]]] 
> 
>   > I agree that just implementing everything in a function is 
>   > reasonable.  For that, just using pcase seems good enough, 
>   > although it might be interesting to have a very different 
>   > way of  writing a function such as 
> 
>   > (pcase-defun mytest 
>   >   "Demonstrates a way of writing defuns via pcase matching." 
>   >   ((a b _) "a b match") (`(c ,v _) (format "c %s match" v))) 
> 
> This avoids the split-up-definition problems I was talking 
> about, since it is all in one place with one name. 
> 
> Please make the defining form's name follow th convention of 
> starting with `def', so people and tools will recognize that it 
> is a defining form. 
> 
> Also, why use `pcase' in the name?  According to the docs of 
> `pcase', this pattern matching is not similar.

This really is the same as pcase, with some transformation to make 
the backquote-style and other style equivalent. For example, the 
above would translate to:

(pcase val
       ((seq 'a 'b _) "a b match")
       (`(c ,v ,_) (format "c %s match" v)))

I think we could probably just use pcase style, but I wanted to 
make the style more consistent between backquote and normal pcase 
matching.  For example, backquote matching can match a sequence of 
matches, but normal matching would require the "seq" matcher at 
the start to do the same thing.

It could be that the way I transform these can be improved, in 
fact, maybe the following would be better:

(def-pcase mytest 
   "Demonstrates a way of writing defuns via pcase matching." 
   (('a 'b '_) "a b match") (('c v '_) (format "c %s match" v)))

Or, as you say, perhaps if we use the name pcase we should just be 
completely consistent with how pcase does things, and not to make 
it more internally self-consistent.

>
>
> -- 
> Dr Richard Stallman (https://stallman.org)
> Chief GNUisance of the GNU Project (https://gnu.org)
> Founder, Free Software Foundation (https://fsf.org)
> Internet Hall-of-Famer (https://internethalloffame.org)



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2021-12-19  4:53 pcase defuns Andrew Hyatt
                   ` (2 preceding siblings ...)
  2021-12-20  4:43 ` Richard Stallman
@ 2021-12-23  2:30 ` Po Lu
  2022-03-26 17:41 ` Andrew Hyatt
  4 siblings, 0 replies; 25+ messages in thread
From: Po Lu @ 2021-12-23  2:30 UTC (permalink / raw)
  To: Andrew Hyatt; +Cc: emacs-devel

Andrew Hyatt <ahyatt@gmail.com> writes:

> Hi all,
>
> As a part of a personal project, I wrote a way to define functions in
> an equivalent way to pcases.  For example:
>
> (pcase-defun mytest (a b _)
>  "Match on 'a 'b with the third argument a wildcard"
>  "a b match")
>
> (pcase-defun mytest (c ,var _)
>  "Match on 'c binding VAR, with the third argument a wildcard"
>  (format "c %s match" var) )
>
> (mytest 'a 'b 'c) -> "a b match"
> (mytest 'c 100 'c) -> "c 100 match"
>
> This is all accomplished by a few small but tricky macros and a
> hashtable that holds all the rules.
>
> I find this method of programming quite useful for certain kinds of
> problems, and pcase is close to what I want, but using pcase forced me
> to encapsulate all of my pattern matching logic in one function. Being
> able to have it spread out in pattern matching functions seems much
> nicer to me.
>
> If this is of interest, I can either add this to the pcase module
> itself, create a package in gnu elpa, or (if there's some reason that
> it shouldn't be done) just keep it in a personal repository.
>
> I'll wait for some guidance before creating a final version, with
> tests, wherever it should go.

That would be awfully confusing, even more so than CL generic functions.
It would make locating the right definition of a function an unwarranted
chore.

With a generic function, the methods can usually be found by a simple
search and are usually obvious from context, and the calling convention
is readily available.

That is confusing enough, but with these "pcase defuns", there is no
standard calling convention to advertise.  Now imagine if these pcase
defuns are scattered over different files.

I think the better solution is to do something like this:

  (defun foo (&rest args)
    (pcase args ...

That avoids most of the confusion mentioned above.



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2021-12-23  1:52           ` Andrew Hyatt
@ 2021-12-24  4:13             ` Richard Stallman
  0 siblings, 0 replies; 25+ messages in thread
From: Richard Stallman @ 2021-12-24  4:13 UTC (permalink / raw)
  To: Andrew Hyatt; +Cc: monnier, emacs-devel

[[[ To any NSA and FBI agents reading my email: please consider    ]]]
[[[ whether defending the US Constitution against all enemies,     ]]]
[[[ foreign or domestic, requires you to follow Snowden's example. ]]]

In some sense, any sort of pattern matching and extraction is
equivalent to any other.  They differ in the details.  Since this
is different from pcase in the details, calling it "pcase" would give
people the wrong idea.  Please call it a different name.

-- 
Dr Richard Stallman (https://stallman.org)
Chief GNUisance of the GNU Project (https://gnu.org)
Founder, Free Software Foundation (https://fsf.org)
Internet Hall-of-Famer (https://internethalloffame.org)





^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2021-12-19  4:53 pcase defuns Andrew Hyatt
                   ` (3 preceding siblings ...)
  2021-12-23  2:30 ` Po Lu
@ 2022-03-26 17:41 ` Andrew Hyatt
  2022-03-27  9:31   ` Stefan Monnier
  2022-03-28  4:15   ` Richard Stallman
  4 siblings, 2 replies; 25+ messages in thread
From: Andrew Hyatt @ 2022-03-26 17:41 UTC (permalink / raw)
  To: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1040 bytes --]


Based on the results of the ensuing discussion, I decided to 
refine my macro to just be one coherent defun.  I experimented 
with a few different ways to do this, but settled on something 
that seemed relatively clear and minimal, with pattern matchers 
that seemed like a better fit for arglists than what exists in 
pcase already (but it's all pcase under the hood).

To give a flavor of what it looks like in practice, here's a few 
pattern defuns from the test:

(defun-pattern fibonacci
    "Compute the fibonacci sequence."
     ((0) 0)
     ((1) 1)
     ((n)
       (+ (fibonacci (- n 1))
            (fibonacci (- n 2)))))

(defun-pattern crit
  "D&D 5e critical hit computation, taking in the results of a d20
and a list of attributes currently in effect.  Returns whether the
results are a crit or not."
   ((20 _) t)
   ((19 (pred (member 'improved-crit))) t))

I'm attaching the package, which works, although there's some 
non-ideal parts of the implementation.  If people think it is 
valuable, I can add this to GNU ELPA.


[-- Attachment #2: defun-pattern.el --]
[-- Type: application/emacs-lisp, Size: 3726 bytes --]

[-- Attachment #3: defun-pattern-test.el --]
[-- Type: application/emacs-lisp, Size: 1656 bytes --]

[-- Attachment #4: Type: text/plain, Size: 1244 bytes --]



On Sat, Dec 18, 2021 at 11:53 PM Andrew Hyatt <ahyatt@gmail.com> 
wrote: 

> Hi all,
>
> As a part of a personal project, I wrote a way to define functions 
> in an equivalent way to pcases.  For example:
>
> (pcase-defun mytest (a b _)
>   "Match on 'a 'b with the third argument a wildcard"
>   "a b match")
>
> (pcase-defun mytest (c ,var _)
>   "Match on 'c binding VAR, with the third argument a wildcard"
>   (format "c %s match" var) )
>
> (mytest 'a 'b 'c) -> "a b match"
> (mytest 'c 100 'c) -> "c 100 match"
>
> This is all accomplished by a few small but tricky macros and a 
> hashtable that holds all the rules.
>
> I find this method of programming quite useful for certain kinds 
> of problems, and pcase is close to what I want, but using pcase 
> forced me to encapsulate all of my pattern matching logic in one 
> function. Being able to have it spread out in pattern matching 
> functions seems much nicer to me.
>
> If this is of interest, I can either add this to the pcase module 
> itself, create a package in gnu elpa, or (if there's some reason 
> that it shouldn't be done) just keep it in a personal repository.
>
> I'll wait for some guidance before creating a final version, with 
> tests, wherever it should go.

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2022-03-26 17:41 ` Andrew Hyatt
@ 2022-03-27  9:31   ` Stefan Monnier
  2022-03-27 18:17     ` Andrew Hyatt
  2022-03-28  4:15   ` Richard Stallman
  1 sibling, 1 reply; 25+ messages in thread
From: Stefan Monnier @ 2022-03-27  9:31 UTC (permalink / raw)
  To: Andrew Hyatt; +Cc: emacs-devel

> (defun-pattern fibonacci
>    "Compute the fibonacci sequence."
>     ((0) 0)
>     ((1) 1)
>     ((n)
>       (+ (fibonacci (- n 1))
>            (fibonacci (- n 2)))))

If you go and benchmark this function, you'll see that its
performance sucks even more than the usual naive fibonacci that it
appears to be implementing.


        Stefan




^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2022-03-27  9:31   ` Stefan Monnier
@ 2022-03-27 18:17     ` Andrew Hyatt
  0 siblings, 0 replies; 25+ messages in thread
From: Andrew Hyatt @ 2022-03-27 18:17 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

On Sun, Mar 27, 2022 at 05:31 AM Stefan Monnier 
<monnier@iro.umontreal.ca> wrote: 

>> (defun-pattern fibonacci 
>>    "Compute the fibonacci sequence." 
>>     ((0) 0) ((1) 1) ((n) 
>>       (+ (fibonacci (- n 1)) 
>>            (fibonacci (- n 2))))) 
> 
> If you go and benchmark this function, you'll see that its 
> performance sucks even more than the usual naive fibonacci that 
> it appears to be implementing.

Sure, I believe that. If you macroexpand-1 this, you'll see that 
it's implemented with pcase in a pretty straighforward manner, so 
this isn't adding overhead beyond pcase's, I believe. My example 
is about showing how the defun-pattern looks in practice, not 
recommending that using it for computation-intensive functions is 
a particularly good idea.

I see this as trading performance for clarity. Now that you 
mention it, I'll note this in the docstring.
>
>
>         Stefan



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2022-03-26 17:41 ` Andrew Hyatt
  2022-03-27  9:31   ` Stefan Monnier
@ 2022-03-28  4:15   ` Richard Stallman
  2022-03-30  1:28     ` Andrew Hyatt
  1 sibling, 1 reply; 25+ messages in thread
From: Richard Stallman @ 2022-03-28  4:15 UTC (permalink / raw)
  To: Andrew Hyatt; +Cc: emacs-devel

[[[ To any NSA and FBI agents reading my email: please consider    ]]]
[[[ whether defending the US Constitution against all enemies,     ]]]
[[[ foreign or domestic, requires you to follow Snowden's example. ]]]

  > To give a flavor of what it looks like in practice, here's a few 
  > pattern defuns from the test:

  > (defun-pattern fibonacci
  >     "Compute the fibonacci sequence."
  >      ((0) 0)
  >      ((1) 1)
  >      ((n)
  >        (+ (fibonacci (- n 1))
  >             (fibonacci (- n 2)))))

For defining how to compute the value, it is clear and simple.
(Do the patterns handle decomposing lists and other compound objects?)
But defining how to compute the value is not the only job a defun
needs to do.  It also needs to record argument names.

Also, it doesn't seem to offer a way to make the function accept other
arguments beyond the one that the pattern will be applied to.

So I think it should take an argument list, and bind all the argument
variables, like this:

    (defun-pattern fibonacci (n)
        "Compute the Nth fibonacci number."
         ((0) 0)
         ((1) 1)
         (t
           (+ (fibonacci (- n 1))
              (fibonacci (- n 2)))))


    (defun-pattern sum (x y)
        "Compute the sum of natural numbers X and Y."
         ((0) y)
         (t
           (1+ (add (1- x) y))))

-- 
Dr Richard Stallman (https://stallman.org)
Chief GNUisance of the GNU Project (https://gnu.org)
Founder, Free Software Foundation (https://fsf.org)
Internet Hall-of-Famer (https://internethalloffame.org)





^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2022-03-28  4:15   ` Richard Stallman
@ 2022-03-30  1:28     ` Andrew Hyatt
  2022-03-31  4:27       ` Richard Stallman
  0 siblings, 1 reply; 25+ messages in thread
From: Andrew Hyatt @ 2022-03-30  1:28 UTC (permalink / raw)
  To: rms; +Cc: emacs-devel


On Mon, Mar 28, 2022 at 12:15 AM Richard Stallman <rms@gnu.org> 
wrote: 

> [[[ To any NSA and FBI agents reading my email: please consider 
> ]]] [[[ whether defending the US Constitution against all 
> enemies,     ]]] [[[ foreign or domestic, requires you to follow 
> Snowden's example. ]]] 
> 
>   > To give a flavor of what it looks like in practice, here's a 
>   > few  pattern defuns from the test: 
> 
>   > (defun-pattern fibonacci 
>   >     "Compute the fibonacci sequence." 
>   >      ((0) 0) ((1) 1) ((n) 
>   >        (+ (fibonacci (- n 1)) 
>   >             (fibonacci (- n 2))))) 
> 
> For defining how to compute the value, it is clear and simple. 
> (Do the patterns handle decomposing lists and other compound 
> objects?)

Yes, it does this.
 
> But defining how to compute the value is not the only job a 
> defun needs to do.  It also needs to record argument names.

The way I look at it, it does do this, but it's just structured 
differently. The normal defun as one arglist. Mine has one per 
matching clause, which means that it can take a variety of 
different arguments, all matching. And the arglist and the 
matching clauses are the same thing, so the arglist can be (n), or 
('foo n), or (1 2 (3 n)), etc.  Yes, it's weird, but I think the 
differentiation here is useful, see my next point.

> Also, it doesn't seem to offer a way to make the function accept 
> other arguments beyond the one that the pattern will be applied 
> to. 
> 
> So I think it should take an argument list, and bind all the 
> argument variables, like this: 
> 
>     (defun-pattern fibonacci (n) 
>         "Compute the Nth fibonacci number." 
>          ((0) 0) ((1) 1) (t 
>            (+ (fibonacci (- n 1)) 
>               (fibonacci (- n 2))))) 
>  
>     (defun-pattern sum (x y) 
>         "Compute the sum of natural numbers X and Y." 
>          ((0) y) (t 
>            (1+ (add (1- x) y))))


Thank you for the feedback and your examples.

There's a tension here: if I make this fit more with existing 
patterns, it becomes something that seems closer to just a defun 
with a pcase in it. Maybe a bit cleaner. In particular, with your 
proposal, we lose the ability to have fairly different arg 
patterns, with different numbers of args, or different 
destructuring patterns. However, I admit that my proposal, without 
a single arglist, is odd, and may be seen as too esoteric by most.

I'm curious to hear more opinions.

>
> -- 
> Dr Richard Stallman (https://stallman.org)
> Chief GNUisance of the GNU Project (https://gnu.org)
> Founder, Free Software Foundation (https://fsf.org)
> Internet Hall-of-Famer (https://internethalloffame.org)



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2022-03-30  1:28     ` Andrew Hyatt
@ 2022-03-31  4:27       ` Richard Stallman
  2022-04-17 22:09         ` Andrew Hyatt
  0 siblings, 1 reply; 25+ messages in thread
From: Richard Stallman @ 2022-03-31  4:27 UTC (permalink / raw)
  To: Andrew Hyatt; +Cc: emacs-devel

[[[ To any NSA and FBI agents reading my email: please consider    ]]]
[[[ whether defending the US Constitution against all enemies,     ]]]
[[[ foreign or domestic, requires you to follow Snowden's example. ]]]

  > The way I look at it, it does do this, but it's just structured 
  > differently. The normal defun as one arglist. Mine has one per 
  > matching clause, which means that it can take a variety of 
  > different arguments, all matching. And the arglist and the 
  > matching clauses are the same thing, so the arglist can be (n), or 
  > ('foo n), or (1 2 (3 n)), etc.  Yes, it's weird, but I think the 
  > differentiation here is useful, see my next point.

The argument list is not just a concept in users' minds.
The command C-h f, which shows documentation for a function,
displays the function's argument list.  If you try C-h f cons RET,
you'll see what I mean.

See also `func-arity'.

So there is a practical reason for defining constructs to
set up the function's argument list.

  > In particular, with your 
  > proposal, we lose the ability to have fairly different arg 
  > patterns, with different numbers of args, or different 
  > destructuring patterns.

Not necessarily.  You could specify `&rest args' for an arglist that
conveys no information.  But users find it useful to see in a simple
way what the arguments of the function should be.


-- 
Dr Richard Stallman (https://stallman.org)
Chief GNUisance of the GNU Project (https://gnu.org)
Founder, Free Software Foundation (https://fsf.org)
Internet Hall-of-Famer (https://internethalloffame.org)





^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2022-03-31  4:27       ` Richard Stallman
@ 2022-04-17 22:09         ` Andrew Hyatt
  2022-04-19  3:48           ` Richard Stallman
  0 siblings, 1 reply; 25+ messages in thread
From: Andrew Hyatt @ 2022-04-17 22:09 UTC (permalink / raw)
  To: rms; +Cc: emacs-devel

On Thu, Mar 31, 2022 at 12:27 AM Richard Stallman <rms@gnu.org> 
wrote: 

> [[[ To any NSA and FBI agents reading my email: please consider 
> ]]] [[[ whether defending the US Constitution against all 
> enemies,     ]]] [[[ foreign or domestic, requires you to follow 
> Snowden's example. ]]] 
> 
>   > The way I look at it, it does do this, but it's just 
>   > structured  differently. The normal defun as one arglist. 
>   > Mine has one per  matching clause, which means that it can 
>   > take a variety of  different arguments, all matching. And 
>   > the arglist and the  matching clauses are the same thing, so 
>   > the arglist can be (n), or  ('foo n), or (1 2 (3 n)), etc. 
>   > Yes, it's weird, but I think the  differentiation here is 
>   > useful, see my next point. 
> 
> The argument list is not just a concept in users' minds.  The 
> command C-h f, which shows documentation for a function, 
> displays the function's argument list.  If you try C-h f cons 
> RET, you'll see what I mean. 
> 
> See also `func-arity'. 
> 
> So there is a practical reason for defining constructs to set up 
> the function's argument list. 
> 
>   > In particular, with your  proposal, we lose the ability to 
>   > have fairly different arg  patterns, with different numbers 
>   > of args, or different  destructuring patterns. 
> 
> Not necessarily.  You could specify `&rest args' for an arglist 
> that conveys no information.  But users find it useful to see in 
> a simple way what the arguments of the function should be.

Thanks for the suggestion.  I've implemented a new version along 
the lines you suggested.  So now things look like:

(defun-pattern fibonacci (n)
    "Compute the fibonacci sequence."
        (0 0)
        (1 1)
        (n
            (+ (fibonacci (- n 1))
                 (fibonacci (- n 2)))))

and

(defun-pattern repeatedp (&rest args)
    "Test for repeated pattern, returns nil, 'once, or 'twice, or 
'split."
    ((a a) 'once)
    ((a a a) 'twice)
    ((a b a) 'split))

With this, I think I've answered your objections.  Do you have an
opinion on what I should do with this?  I can check this in the emacs
source code, as a GNU elpa package, or just put it in an external repository.
>
>
> -- 
> Dr Richard Stallman (https://stallman.org)
> Chief GNUisance of the GNU Project (https://gnu.org)
> Founder, Free Software Foundation (https://fsf.org)
> Internet Hall-of-Famer (https://internethalloffame.org)



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: pcase defuns
  2022-04-17 22:09         ` Andrew Hyatt
@ 2022-04-19  3:48           ` Richard Stallman
  0 siblings, 0 replies; 25+ messages in thread
From: Richard Stallman @ 2022-04-19  3:48 UTC (permalink / raw)
  To: Andrew Hyatt; +Cc: emacs-devel

[[[ To any NSA and FBI agents reading my email: please consider    ]]]
[[[ whether defending the US Constitution against all enemies,     ]]]
[[[ foreign or domestic, requires you to follow Snowden's example. ]]]

  > Thanks for the suggestion.  I've implemented a new version along 
  > the lines you suggested.  So now things look like:

  > (defun-pattern fibonacci (n)
  >     "Compute the fibonacci sequence."

The doc string should use the arguments
and describe what an individual call does.
Thusm

    Return the Nth fibonacci number.

Aside from that, I have no further comments.
I don't have an opinion about how useful this is.




^ permalink raw reply	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2022-04-19  3:48 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-12-19  4:53 pcase defuns Andrew Hyatt
2021-12-19  8:34 ` Tassilo Horn
2021-12-19 15:33   ` Andrew Hyatt
2021-12-19 17:16     ` Tassilo Horn
2021-12-19 19:05       ` Stefan Monnier
2021-12-20  5:56         ` Tassilo Horn
2021-12-22 14:07   ` LdBeth
2021-12-19 17:23 ` Stefan Monnier
2021-12-19 21:08   ` Andrew Hyatt
2021-12-21  4:15     ` Richard Stallman
2021-12-21  5:20       ` Andrew Hyatt
2021-12-22  4:18         ` Richard Stallman
2021-12-23  1:52           ` Andrew Hyatt
2021-12-24  4:13             ` Richard Stallman
2021-12-21 15:32     ` Stefan Monnier
2021-12-20  4:43 ` Richard Stallman
2021-12-23  2:30 ` Po Lu
2022-03-26 17:41 ` Andrew Hyatt
2022-03-27  9:31   ` Stefan Monnier
2022-03-27 18:17     ` Andrew Hyatt
2022-03-28  4:15   ` Richard Stallman
2022-03-30  1:28     ` Andrew Hyatt
2022-03-31  4:27       ` Richard Stallman
2022-04-17 22:09         ` Andrew Hyatt
2022-04-19  3:48           ` Richard Stallman

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).