unofficial mirror of help-gnu-emacs@gnu.org
 help / color / mirror / Atom feed
* How to extract bindings from `pcase-let*`?
@ 2021-03-15  1:43 Okam
  2021-03-15 15:38 ` Stefan Monnier
  0 siblings, 1 reply; 6+ messages in thread
From: Okam @ 2021-03-15  1:43 UTC (permalink / raw)
  To: help-gnu-emacs

Hello,

I would like to extract the bindings created by `pcase-let*` to use them
in a `setq` form.  Is there a good way of doing this?

Previously, I was using `macroexpand` on `pcase-let*` and extracting the
bindings from the list, but after the expansion produced by `pcase-let*`
changed recently, I realized that this way assumes too much.

Thank you.




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

* Re: How to extract bindings from `pcase-let*`?
  2021-03-15  1:43 How to extract bindings from `pcase-let*`? Okam
@ 2021-03-15 15:38 ` Stefan Monnier
  2021-03-16  1:50   ` Okam
  0 siblings, 1 reply; 6+ messages in thread
From: Stefan Monnier @ 2021-03-15 15:38 UTC (permalink / raw)
  To: help-gnu-emacs

> I would like to extract the bindings created by `pcase-let*` to use them
> in a `setq` form.  Is there a good way of doing this?

Sending me a patch to `pcase.el` which does that?

> Previously, I was using `macroexpand` on `pcase-let*` and extracting the
> bindings from the list, but after the expansion produced by `pcase-let*`
> changed recently, I realized that this way assumes too much.

Interesting.  Can you show me the code you used?


        Stefan




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

* Re: How to extract bindings from `pcase-let*`?
  2021-03-15 15:38 ` Stefan Monnier
@ 2021-03-16  1:50   ` Okam
  2021-03-16  2:45     ` Stefan Monnier
  0 siblings, 1 reply; 6+ messages in thread
From: Okam @ 2021-03-16  1:50 UTC (permalink / raw)
  To: help-gnu-emacs

On 3/15/21 11:38 AM, Stefan Monnier wrote:
>
>> I would like to extract the bindings created by `pcase-let*` to use them
>> in a `setq` form.  Is there a good way of doing this?
>
> Sending me a patch to `pcase.el` which does that?

OK. I wanted to see if there was an intentional entry point first.

>> Previously, I was using `macroexpand` on `pcase-let*` and extracting the
>> bindings from the list, but after the expansion produced by `pcase-let*`
>> changed recently, I realized that this way assumes too much.
>
> Interesting.  Can you show me the code you used?

This is the simple code I used:

     (defun loopy-pcase--get-variable-values (var val)
       "Destructure VAL according to VAR using `pcase'.

     Return a list of 2 sub-lists: (1) the needed generated variables
     and (2) the variables actually named in VAR.

     VAR should be a normal `pcase' destructuring pattern, such as
     \"`(a . ,b)\" or \"`(1 2 3 . ,rest)\"."
       ;; Using `pcase-let*' as an interface, since it is a public function.
       ;; `pcase' knows to not assign variables if they are unused, so
we pass
       ;; back in `var' (a backquoted list) so that it thinks the variables
       ;; are used.
       ;;
       ;; This will give a form like
       ;; (let* (temp-vars) (let (actual-vars) VAR))
       ;;
       ;; NOTE: Named variables might be in reverse order.  Not sure if
this is
       ;; reliable behavior.
       (pcase-let* ((`(let* ,temp-vars (let ,true-vars . ,_))
                     (macroexpand `(pcase-let* ((,var ,val)) ,var))))
         (list temp-vars true-vars)))

It assumes too much, and is not resilient to changes in how `pcase-let*`
expands. I don't fully understand the code of Pcase yet, and failed to
find something like Dash's `dash--match`, which returns a list of
bindings that can be substituted into forms like `setq` or `let*`.

I am doing this to attempt to optionally use Pcase for destructuring in
a macro that I am writing.




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

* Re: How to extract bindings from `pcase-let*`?
  2021-03-16  1:50   ` Okam
@ 2021-03-16  2:45     ` Stefan Monnier
  2021-03-17  1:33       ` Okam
  0 siblings, 1 reply; 6+ messages in thread
From: Stefan Monnier @ 2021-03-16  2:45 UTC (permalink / raw)
  To: help-gnu-emacs

>        (pcase-let* ((`(let* ,temp-vars (let ,true-vars . ,_))
>                      (macroexpand `(pcase-let* ((,var ,val)) ,var))))
>          (list temp-vars true-vars)))

Indeed it's not reliable: for a case like

    (pcase-let (((or `(inorder ,x ,y) `(reverse ,y ,x)) FOO))
      (cons x y))

it will generate code comparable to

    (if (eq (car FOO) 'inorder)
        (cons (nth 1 FOO) (nth 2 FOO))
      (cons (nth 2 FOO) (nth 1 FOO))

[ Just obfuscated with gensym'd vars and such ]

And if you replace the trivial (cons x y) with something less trivial it
will turn into something comparable to:

    (let ((body (lambda (x y) <something-less-trivial>)))
      (if (eq (car FOO) 'inorder)
          (funcall body (nth 1 FOO) (nth 2 FOO))
        (funcall body (nth 2 FOO) (nth 1 FOO))))

so it's far from clear how to represent this kind of code in the form of
"a list of bindings that can be substituted into forms like `setq` or
`let*`"

OTOH, I think `pcase.el` could offer a function which could take
a "buildbody" function as argument, where that buildbody would receive
something like the list (x y) and would be expected to return the
(lambda (x y) <something-less-trivial>) to use.  So you could pass it a "buildbody"
function which takes the list (x y) and returns something like
(lambda (tmp1 tmp2) (setq x tmp1 y tmp2)), which I expect would do more
or less what you want.

> I am doing this to attempt to optionally use Pcase for destructuring in
> a macro that I am writing.

`pcase-let` already does destructuring, so could you explain in a bit
more detail what kind of destructuring you're looking for (and why)?


        Stefan




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

* Re: How to extract bindings from `pcase-let*`?
  2021-03-16  2:45     ` Stefan Monnier
@ 2021-03-17  1:33       ` Okam
  2021-03-17  2:59         ` Stefan Monnier
  0 siblings, 1 reply; 6+ messages in thread
From: Okam @ 2021-03-17  1:33 UTC (permalink / raw)
  To: help-gnu-emacs

On 3/15/21 10:45 PM, Stefan Monnier wrote:
>
>>         (pcase-let* ((`(let* ,temp-vars (let ,true-vars . ,_))
>>                       (macroexpand `(pcase-let* ((,var ,val)) ,var))))
>>           (list temp-vars true-vars)))
>
> Indeed it's not reliable: for a case like
>
>      (pcase-let (((or `(inorder ,x ,y) `(reverse ,y ,x)) FOO))
>        (cons x y))
>
> it will generate code comparable to
>
>      (if (eq (car FOO) 'inorder)
>          (cons (nth 1 FOO) (nth 2 FOO))
>        (cons (nth 2 FOO) (nth 1 FOO))
>
> [ Just obfuscated with gensym'd vars and such ]
>
> And if you replace the trivial (cons x y) with something less trivial it
> will turn into something comparable to:
>
>      (let ((body (lambda (x y) <something-less-trivial>)))
>        (if (eq (car FOO) 'inorder)
>            (funcall body (nth 1 FOO) (nth 2 FOO))
>          (funcall body (nth 2 FOO) (nth 1 FOO))))
>
> so it's far from clear how to represent this kind of code in the form of
> "a list of bindings that can be substituted into forms like `setq` or
> `let*`"
>
> OTOH, I think `pcase.el` could offer a function which could take
> a "buildbody" function as argument, where that buildbody would receive
> something like the list (x y) and would be expected to return the
> (lambda (x y) <something-less-trivial>) to use.  So you could pass it a "buildbody"
> function which takes the list (x y) and returns something like
> (lambda (tmp1 tmp2) (setq x tmp1 y tmp2)), which I expect would do more
> or less what you want.
>
>> I am doing this to attempt to optionally use Pcase for destructuring in
>> a macro that I am writing.
>
> `pcase-let` already does destructuring, so could you explain in a bit
> more detail what kind of destructuring you're looking for (and why)?
>
>
>          Stefan
>
>

I am writing a looping macro similar to `cl-loop`, and have allowed for
destructuring in accumulation clauses.  Here are some examples:

     ;; Summing the nth elements of arrays:
     ;; => (8 10 12 14 16 18)
     (loopy (list (list-elem1 list-elem2)
                  '(([1 2 3] [4 5 6])
                    ([7 8 9] [10 11 12])))
            (sum [sum1 sum2 sum3] list-elem1)
            (sum [sum4 sum5 sum6] list-elem2))

     ;; Or, more simply:
     ;; => (8 10 12 14 16 18)
     (loopy (list list-elem '(([1 2 3] [4 5 6])
                              ([7 8 9] [10 11 12])))
            (sum ([sum1 sum2 sum3] [sum4 sum5 sum6])
                 list-elem))

     ;; Separate the elements of sub-list:
     ;; => ((1 3) (2 4))
     (loopy (list i '((1 2) (3 4)))
            (collect (elem1 elem2) i))

There is a built-in destructuring system for this in the macro, but a
user requested a way to use other destructuring systems, such as Dash,
Pcase, or `seq-let`.

To do this for accumulation, I don't want to actually assign the values
determined by Dash or Pcase to the variables named by the user.
Instead, I want to accumulate those values into the named variables.

Here is an example of the macro that I tested with Pcase (before I
realized my mistake)

     ;; => ((1 4) (3 6))
     (loopy (flag pcase)
            (list elem '((1 (2 3)) (4 (5 6))))
            (collect `(,a (,_ ,b)) elem))

in which the `collect` expression expands into something like

     (setq a (append a (list some-value-from-elem)))
     (setq b (append b (list some-other-value-from-elem)))

where `some-value-from-elem` and `some-other-value-from-elem` are
determined by Pcase, Dash, or other destructuring systems.

Do you think that this is doable using Pcase?

A link to the macro:
https://github.com/okamsn/loopy




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

* Re: How to extract bindings from `pcase-let*`?
  2021-03-17  1:33       ` Okam
@ 2021-03-17  2:59         ` Stefan Monnier
  0 siblings, 0 replies; 6+ messages in thread
From: Stefan Monnier @ 2021-03-17  2:59 UTC (permalink / raw)
  To: help-gnu-emacs

>      ;; => ((1 4) (3 6))
>      (loopy (flag pcase)
>             (list elem '((1 (2 3)) (4 (5 6))))
>             (collect `(,a (,_ ,b)) elem))
>
> in which the `collect` expression expands into something like
>
>      (setq a (append a (list some-value-from-elem)))
>      (setq b (append b (list some-other-value-from-elem)))
>
> where `some-value-from-elem` and `some-other-value-from-elem` are
> determined by Pcase, Dash, or other destructuring systems.
>
> Do you think that this is doable using Pcase?

You could do something like:

    (pcase--u `((,(pcase--match INCOMING (pcase--macroexpand PATTERN))
                 ,(lambda (vars)
                    `(progn
                      . ,(mapcar (lambda (v) `(push ,(cadr v) ,(car v)))
                                 vars))))))

where INCOMING is the incoming data (`elem` in your above example).

As the `--` in there suggest, this is digging into pcase's internals.
I think this code will work with Emacs-28 but the `cadr` would need to
be replaced with `cdr` in earlier versions.

Also, this does a `pcase` rather than a `pcase-let` so it will just
silently do nothing if the pattern doesn't match.  You can force the
`pcase-let` semantics with something like:

    (pcase--u `((,(pcase--match INCOMING
                                (pcase--macroexpand
                                 `(or ,PATTERN pcase--dontcare)))
                 ,(lambda (vars)
                    `(progn
                      . ,(mapcar (lambda (v) `(push ,(cadr v) ,(car v)))
                                 vars))))))

See IELM session below.  I suggest you `M-x report-emacs-bug` to request
that this functionality be made accessible without having to rely on
internals (and put me in the `X-Debbugs-Cc` when you do that).


        Stefan


PS: I used `push` rather than your `setq+append+list` since it is
algorithmically much better behaved (linear instead of quadratic
complexity).


ELISP> (pcase--u `((,(pcase--match 'elem
                                   (pcase--macroexpand
                                    '(or `(,a (,_ ,b)) pcase--dontcare)))
                    ,(lambda (vars)
                       `(progn
                         . ,(mapcar (lambda (v) `(push ,(cadr v) ,(car v)))
                                    vars))))))
(progn
  (ignore (consp elem))
  (let* ((x1507 (car-safe elem))
         (x1508 (cdr-safe elem)))
    (progn (ignore (consp x1508))
           (let* ((x1509 (car-safe x1508)))
             (progn (ignore (consp x1509))
                    (let* ((x1511 (cdr-safe x1509)))
                      (progn (ignore (consp x1511))
                             (let* ((x1512 (car-safe x1511))
                                    (x1513 (cdr-safe x1511)))
                               (progn (ignore (null x1513))
                                      (let* ((x1514 (cdr-safe x1508)))
                                        (progn (ignore (null x1514))
                                               (progn (push x1512 b)
                                                      (push x1507 a)))))))))))))

ELISP> 




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

end of thread, other threads:[~2021-03-17  2:59 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2021-03-15  1:43 How to extract bindings from `pcase-let*`? Okam
2021-03-15 15:38 ` Stefan Monnier
2021-03-16  1:50   ` Okam
2021-03-16  2:45     ` Stefan Monnier
2021-03-17  1:33       ` Okam
2021-03-17  2:59         ` Stefan Monnier

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).