Why is FUNC in cl-callf not allowed to be an expression?

classic Classic list List threaded Threaded
31 messages Options
12
Reply | Threaded
Open this post in threaded view
|

Why is FUNC in cl-callf not allowed to be an expression?

Michael Heerdegen
Hello,

I wonder why the FUNC argument in cl-callf is restricted to symbols (and
lambda expressions, though this is not documented).  I don't see any
reason to disallow arbitrary expressions, besides, maybe, that symbols
would become ambiguous.

I really would like to have an gv-callf that would simply interpret the
function argument as an expression.  cl-callf just saves the programmer
from typing the #' but forbids many use cases.

I think I would even like it most like this:

(defmacro gv-callf (call &optional n)
  (gv-letplace (_getter setter) (nth (or n 1) call)
    (funcall setter call)))

Example:

(let ((l (list 1 2)))
  (gv-callf (append l (list 3 4)))
  l)

=> (1 2 3 4)

(let ((l (list (list 'c 'd))))
  (gv-callf (append (list 'a 'b) (car l) (list 'e 'f)) 2)
  l)

=> ((a b c d e f))

That's simpler than current cl-callf, unites cl-callf and cl-callf2 in
something more general, and even eldoc still works in the CALL.


Michael.

Reply | Threaded
Open this post in threaded view
|

Re: Why is FUNC in cl-callf not allowed to be an expression?

Stefan Monnier
> I wonder why the FUNC argument in cl-callf is restricted to symbols (and
> lambda expressions, though this is not documented).  I don't see any
> reason to disallow arbitrary expressions, besides, maybe, that symbols
> would become ambiguous.

`callf` has been in cl.el "for ever" but it's not in Common-Lisp, so
I don't really know where it comes from.  I agree that when I moved it
to cl-lib, I missed an opportunity to fix it and make it take a normal
expression as first argument.

> I really would like to have an gv-callf that would simply interpret the
> function argument as an expression.  cl-callf just saves the programmer
> from typing the #' but forbids many use cases.

FWIW, you can circumvent this with

    (cl-flet ((f <EXPR>))
      (cl-callf f ...))

> I think I would even like it most like this:
>
> (defmacro gv-callf (call &optional n)
>   (gv-letplace (_getter setter) (nth (or n 1) call)
>     (funcall setter call)))

It's cute, tho I'm not too fond of specifying the place via a number,
personally,


        Stefan


Reply | Threaded
Open this post in threaded view
|

Re: Why is FUNC in cl-callf not allowed to be an expression?

Michael Heerdegen
Stefan Monnier <[hidden email]> writes:

> > (defmacro gv-callf (call &optional n)
> >   (gv-letplace (_getter setter) (nth (or n 1) call)
> >     (funcall setter call)))
>
> It's cute, tho I'm not too fond of specifying the place via a number,
> personally,

[I should use GETTER btw for more efficient code:

(defmacro gv-callf (call &optional n)
  (unless n (setq n 1))
  (gv-letplace (getter setter) (nth n call)
    (setf (nth n call) getter)
    (funcall setter call)))
]

Yes, but still better than having it as part of the macro name as in
cl-callf2... I don't have any better idea currently.  We could still
extend cl-callf however.


Michael.

Reply | Threaded
Open this post in threaded view
|

Re: Why is FUNC in cl-callf not allowed to be an expression?

Stefan Monnier
>> > (defmacro gv-callf (call &optional n)
>> >   (gv-letplace (_getter setter) (nth (or n 1) call)
>> >     (funcall setter call)))
>>
>> It's cute, tho I'm not too fond of specifying the place via a number,
>> personally,
>
> [I should use GETTER btw for more efficient code:
>
> (defmacro gv-callf (call &optional n)
>   (unless n (setq n 1))
>   (gv-letplace (getter setter) (nth n call)
>     (setf (nth n call) getter)
>     (funcall setter call)))
> ]
>
> Yes, but still better than having it as part of the macro name as in
> cl-callf2... I don't have any better idea currently.  We could still
> extend cl-callf however.

While it doesn't solve the problem, while playing with it I noticed that
I'm less annoyed by the use of a number if I can put it first, as in:

    (gv-callf 2
      (append head (car x) (cdr x)))
     
I'm not sure I can say why, tho.


        Stefan

Reply | Threaded
Open this post in threaded view
|

Re: Why is FUNC in cl-callf not allowed to be an expression?

Stefan Monnier
> While it doesn't solve the problem, while playing with it I noticed that
> I'm less annoyed by the use of a number if I can put it first, as in:
>
>     (gv-callf 2
>       (append head (car x) (cdr x)))
>      
> I'm not sure I can say why, tho.

Now that I look at it again, I guess an "anamorphic" version would be
probably more obvious:

    (gv-modify (nth i l)
       (append head it (cdr x)))

Implementation left as an exercise for the reader,


        Stefan


Reply | Threaded
Open this post in threaded view
|

Re: Why is FUNC in cl-callf not allowed to be an expression?

Stefan Monnier
> Now that I look at it again, I guess an "anamorphic" version would be
> probably more obvious:
>
>     (gv-modify (nth i l)
>        (append head it (cdr x)))

And I see it solves another downside of (gv-callf N EXP), which is that in

    (gv-callf 2 (append HEAD (nth INDEX LIST) TAIL))
   
the evaluation order will actually end up being

    INDEX; LIST; HEAD; TAIL
   
which is counter-intuitive.


        Stefan


Reply | Threaded
Open this post in threaded view
|

Re: Why is FUNC in cl-callf not allowed to be an expression?

Michael Heerdegen
Stefan Monnier <[hidden email]> writes:

> > Now that I look at it again, I guess an "anamorphic" version would be
> > probably more obvious:
> >
> >     (gv-modify (nth i l)
> >        (append head it (cdr x)))

I guess I would rather like it with explicit naming:

(defmacro gv-modify (binding call)
  (declare (indent 1))
  (pcase-let ((`(,var ,place) binding))
    (gv-letplace (getter setter) place
      (funcall setter `(let ((,var ,getter)) ,call)))))

Example:

(let ((l '(x ((2 3)))))
  (gv-modify (plc (car (cadr l)))
    (append '(0 1) plc '(4 5)))
  l)

=> (x ((0 1 2 3 4 5)))

> And I see it solves another downside of (gv-callf N EXP), which is that in
>
>     (gv-callf 2 (append HEAD (nth INDEX LIST) TAIL))
>
> the evaluation order will actually end up being
>
>     INDEX; LIST; HEAD; TAIL
>
> which is counter-intuitive.

The disadvantage is that the `append' call looks a bit pulled apart, but
I think it would be ok for me.


Michael.

Reply | Threaded
Open this post in threaded view
|

Re: Why is FUNC in cl-callf not allowed to be an expression?

Stefan Monnier
> (defmacro gv-modify (binding call)

Why not (gv-modify VAR PLACE EXP) ?
I mean, is there any hope/possibility we'll ever want to extend
`binding` to anything else than "a single VAR and a single PLACE"?

Or do you actually like having the extra set of parens around the
VAR-PLACE pair?


        Stefan


Reply | Threaded
Open this post in threaded view
|

Re: Why is FUNC in cl-callf not allowed to be an expression?

Michael Heerdegen
Stefan Monnier <[hidden email]> writes:

> > (defmacro gv-modify (binding call)
>
> Why not (gv-modify VAR PLACE EXP) ?
> I mean, is there any hope/possibility we'll ever want to extend
> `binding` to anything else than "a single VAR and a single PLACE"?

Yeah - I was on a trip, and the first thing that occurred to my when I
was traveling was that these extra parens are nonsense.

Michael.

Reply | Threaded
Open this post in threaded view
|

Re: Why is FUNC in cl-callf not allowed to be an expression?

Michael Heerdegen
In reply to this post by Stefan Monnier
Stefan Monnier <[hidden email]> writes:

> > (defmacro gv-modify (binding call)

Here is another idea:

(defmacro gv-with-place (place f)
  (declare (indent 1))
  (gv-letplace (getter setter) place
    (let ((v (make-symbol "v")))
      `(funcall ,f ,getter (lambda (,v) ,(funcall setter v))))))

Example:

(let ((x '((a))))
  (gv-with-place (car x)
    (lambda (v set)
      (funcall set (append '(1 2) v '(3 4)))))
  x)

=> ((1 2 a 3 4))

The usage looks a bit longer than in all other proposals, but I find it
cleaner.  It's also more general, you can e.g. make setting conditional
as in your (commented) `gv-pushnew!'.  The second arg of `gv-with-place'
being a function also gives a chance for factoring.  More function calls
at run time involved, though.

I see that the suggestion comes near to just use `gv-letplace' in the
first place, but I think it's easier to understand for the end
programmer.


Michael.

Reply | Threaded
Open this post in threaded view
|

Re: Why is FUNC in cl-callf not allowed to be an expression?

Stefan Monnier
> (defmacro gv-with-place (place f)
>   (declare (indent 1))
>   (gv-letplace (getter setter) place
>     (let ((v (make-symbol "v")))
>       `(funcall ,f ,getter (lambda (,v) ,(funcall setter v))))))

We could at least avoid the overall `funcall`:

    (defmacro gv-with-place (val setfun place &rest body)
      (declare (indent 3))
      (gv-letplace (getter setter) place
        `(let ((,val ,getter)
               (,setfun (lambda (v) ,(funcall setter 'v))))
           ,@body)))

Along the same lines maybe we could provide a (gv-ref VAL SETFUN)
pcase-macro, so you could do

    (pcase-let (((gv-ref val setter) (gv-ref PLACE)))
      (unless (member X val) (funcall setter (cons X val))))

Even better would be if we could do

    (pcase-let (((gv-ref p) (gv-ref PLACE)))
      (unless (member X p) (setf p (cons X p))))

but it seems this would require non-trivial changes to pcase (since
basically `p` should not be bound there with a `let` but with
a `cl-symbol-macrolet`).

Still, I think the `gv-modify` discussed earlier hits a sweeter spot.


        Stefan

Reply | Threaded
Open this post in threaded view
|

Re: Why is FUNC in cl-callf not allowed to be an expression?

Michael Heerdegen
Stefan Monnier <[hidden email]> writes:

> > (defmacro gv-with-place (place f)
> >   (declare (indent 1))
> >   (gv-letplace (getter setter) place
> >     (let ((v (make-symbol "v")))
> >       `(funcall ,f ,getter (lambda (,v) ,(funcall setter v))))))
>
> We could at least avoid the overall `funcall`:
>
>     (defmacro gv-with-place (val setfun place &rest body)
>       (declare (indent 3))
>       (gv-letplace (getter setter) place
>         `(let ((,val ,getter)
>                (,setfun (lambda (v) ,(funcall setter 'v))))
>            ,@body)))

Yes, or maybe even

(defmacro gv-with-place (val-sym setfun-sym place &rest body)
  (declare (indent 3))
  (gv-letplace (getter setter) place
    (let ((v (make-symbol "v")))
      `(cl-flet ((,setfun-sym (lambda (,v) ,(funcall setter v))))
         (let ((,val-sym ,getter))
           ,@body)))))

Example:

  (let ((l '(2 3 4)))
    (gv-with-place p set (cdr l)
      (unless (memq 1 p)
        (set (cons 1 p))))
    l)

  ==> (2 1 3 4)

> Along the same lines maybe we could provide a (gv-ref VAL SETFUN)
> pcase-macro, so you could do
>
>     (pcase-let (((gv-ref val setter) (gv-ref PLACE)))
>       (unless (member X val) (funcall setter (cons X val))))
>
> Even better would be if we could do
>
>     (pcase-let (((gv-ref p) (gv-ref PLACE)))
>       (unless (member X p) (setf p (cons X p))))
>
> but it seems this would require non-trivial changes to pcase (since
> basically `p` should not be bound there with a `let` but with
> a `cl-symbol-macrolet`).

I tried to implement more or less that as a normal macro:

(defun gv-ad-hoc-place (val _setter)
  (declare (compiler-macro (lambda (_) val))
           (gv-expander funcall))
  val)

(defmacro gv-do-place (place-sym place &rest body)
  (declare (indent 2))
  (gv-letplace (getter setter) place
    `(let ((val ,getter))
       (cl-symbol-macrolet ((,place-sym (gv-ad-hoc-place val ,setter)))
         ,@body))))

Example:
  (let ((l '(2 3 4)))
    (gv-do-place p (cdr l)
      (unless (memq 1 p)
        (setf p (cons 1 p))))
    l)

  ==> (2 1 3 4)

Note that `gv-synthetic-place' can't be used here because in setf it
would just expand to (setq val ...) with val being the let-bound symbol
in the implementation, instead of something setf'ing the PLACE.

> Still, I think the `gv-modify` discussed earlier hits a sweeter spot.

Is it still your favorite?


Michael.

Reply | Threaded
Open this post in threaded view
|

Re: Why is FUNC in cl-callf not allowed to be an expression?

Michael Heerdegen
Michael Heerdegen <[hidden email]> writes:

> (defmacro gv-do-place (place-sym place &rest body)
>   (declare (indent 2))
>   (gv-letplace (getter setter) place
>     `(let ((val ,getter))
>        (cl-symbol-macrolet ((,place-sym (gv-ad-hoc-place val ,setter)))
>          ,@body))))

A better name for this might be "gv-place-let", though that could be
confused with "gv-letplace".

Michael.

Reply | Threaded
Open this post in threaded view
|

Re: Why is FUNC in cl-callf not allowed to be an expression?

Michael Heerdegen
In reply to this post by Stefan Monnier
Stefan Monnier <[hidden email]> writes:

> Even better would be if we could do
>
>     (pcase-let (((gv-ref p) (gv-ref PLACE)))
>       (unless (member X p) (setf p (cons X p))))
>
> but it seems this would require non-trivial changes to pcase (since
> basically `p` should not be bound there with a `let` but with
> a `cl-symbol-macrolet`).

FWIW I'm all for enhancing pcase so that it can establish other types of
bindings in general.

Michael.

Reply | Threaded
Open this post in threaded view
|

Re: Why is FUNC in cl-callf not allowed to be an expression?

Michael Heerdegen
In reply to this post by Michael Heerdegen
Michael Heerdegen <[hidden email]> writes:

> A better name for this might be "gv-place-let", though that could be
> confused with "gv-letplace".

I played with it a bit.  It may make sense to make this "gv-place-let"
accept a binding list and implement a "gv-place-let*".  Here are two
examples where this would be useful:

(let ((x '(2 . 1)))
  (gv-place-let a (car x)
    (gv-place-let b (cdr x)
      (when (< b a) (cl-rotatef a b))))
  x)

==> (1 . 2)

(let ((l '(1 (2 3) 4)))
  (gv-place-let p1 (nth 1 l)
    (gv-place-let p2 (car p1)
      (setq p2 (list p2 (- p2)))))
  l)
==> (1 ((2 -2) 3) 4)


Michael.

Reply | Threaded
Open this post in threaded view
|

Re: Why is FUNC in cl-callf not allowed to be an expression?

Stefan Monnier
In reply to this post by Michael Heerdegen
> (defun gv-ad-hoc-place (val _setter)
>   (declare (compiler-macro (lambda (_) val))
>            (gv-expander funcall))
>   val)
[...]
> Note that `gv-synthetic-place' can't be used here because in setf it
> would just expand to (setq val ...) with val being the let-bound symbol
> in the implementation, instead of something setf'ing the PLACE.

Hmm... that's indeed what I see, but I haven't yet understood why that
is (or rather, why that doesn't happen to your gv-ad-hoc-place).
I guess that qualifies as a bug in gv-synthetic-place (not that it
matters too much: I can't find a single use of it).


        Stefan


Reply | Threaded
Open this post in threaded view
|

Re: Why is FUNC in cl-callf not allowed to be an expression?

Michael Heerdegen
Stefan Monnier <[hidden email]> writes:

> Hmm... that's indeed what I see, but I haven't yet understood why that
> is (or rather, why that doesn't happen to your gv-ad-hoc-place).  I
> guess that qualifies as a bug in gv-synthetic-place (not that it
> matters too much: I can't find a single use of it).

Well, let's see:

;; works:
(let ((l '(0)))
  (cl-symbol-macrolet ((p (gv-synthetic-place (car l) (lambda (v) `(setcar l ,v)))))
    (setf p 1))
  l) ;; ==> (1)

;; dosn't work:
(let ((l '((0))))
  (let ((cl (car l)))
    (cl-symbol-macrolet ((p (gv-synthetic-place cl (lambda (v) `(setcar cl ,v)))))
      (setf p 1)))
  l) ;; ==> ((0))

;; but this works
(let ((l '((0))))
  (let ((cl (car l)))
    (setf (gv-synthetic-place cl (lambda (v) `(setcar cl ,v))) 1))
  l) ;; ((1))

The problem in the second case is that `cl-symbol-macrolet' is too
eager: it also macroexpands the symbol expansion `p' inside the `setf',
i.e. it expands the `gv-synthetic-place' macro call, to just `cl', so
you get (setf cl 1).

That just doesn't happen to my version since it's a function instead of
a macro (and the compiler macro seems to be applied later).

Any suggestions?


Michael.

Reply | Threaded
Open this post in threaded view
|

Re: Why is FUNC in cl-callf not allowed to be an expression?

Michael Heerdegen
Michael Heerdegen <[hidden email]> writes:

> The problem in the second case is that `cl-symbol-macrolet' is too
> eager: it also macroexpands the symbol expansion `p' inside the `setf',
> i.e. it expands the `gv-synthetic-place' macro call, to just `cl', so
> you get (setf cl 1).

Something like this would help (i.e. gv-synthetic-place would
work as expected):


From e6c6e2a360e7970c8c687af580b0fcabc11aaae7 Mon Sep 17 00:00:00 2001
From: Michael Heerdegen <[hidden email]>
Date: Thu, 16 May 2019 17:05:07 +0200
Subject: [PATCH] WIP: Small fix in cl--sm-macroexpand

---
 lisp/emacs-lisp/cl-macs.el | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lisp/emacs-lisp/cl-macs.el b/lisp/emacs-lisp/cl-macs.el
index 8af8fccde7..ce4be75d2b 100644
--- a/lisp/emacs-lisp/cl-macs.el
+++ b/lisp/emacs-lisp/cl-macs.el
@@ -2152,7 +2152,8 @@ cl--sm-macroexpand
              ;; Perform symbol-macro expansion.
              (let ((symval (assq exp venv)))
                (when symval
-                 (setq exp (cadr symval)))))
+                 (setq exp (cadr symval))
+                 nil)))
             (`(setq . ,_)
              ;; Convert setq to setf if required by symbol-macro expansion.
              (let* ((args (mapcar (lambda (f) (macroexpand f env))
--
2.20.1



but I have no clue if it's correct or a good idea.  Symbol macro
bindings to macro forms are rare in the Emacs sources.

Michael.
Reply | Threaded
Open this post in threaded view
|

Re: Why is FUNC in cl-callf not allowed to be an expression?

Stefan Monnier
> Something like this would help (i.e. gv-synthetic-place would
> work as expected):
[...]
> but I have no clue if it's correct or a good idea.

It's not correct in general, no.  But it pointed me to the origin of the
problem.  I installed a fix.

This said, having `gv-expander` on a macro is always risky business
because it makes the result of macroexpansion sensitive to the order in
which things are expanded.  Of course, having both compiler-macro and
gv-expander suffers fundamentally from the same problem, tho to
a lesser extent.

Related to your patch, we should change `macroexpand` to call
`macroexpand-1` and then make `cl-symbol-macrolet` advise
`macroexpand-1` so it doesn't need the `while` loop.


        Stefan


Reply | Threaded
Open this post in threaded view
|

Re: Why is FUNC in cl-callf not allowed to be an expression?

Michael Heerdegen
Stefan Monnier <[hidden email]> writes:

> > Something like this would help (i.e. gv-synthetic-place would
> > work as expected):
> [...]
> > but I have no clue if it's correct or a good idea.
>
> It's not correct in general, no.  But it pointed me to the origin of the
> problem.  I installed a fix.

I had a hard time to understand why that works until I found that
(setf p ...) with symbol p symbol-macro bound is first expanded into
setq (treating p as a simple symbol) and that it treated by your fix
(and gets transformed into a setf again).  Ok, why not.

> Related to your patch, we should change `macroexpand` to call
> `macroexpand-1` and then make `cl-symbol-macrolet` advise
> `macroexpand-1` so it doesn't need the `while` loop.

Sounds reasonable, but I don't volunteer to do it.

What do think now about `gv-place-let'?  Does that look acceptable?

Thanks,

Michael.

12