emacs-devel
[Top][All Lists]
Advanced

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

[PATCH] Some improvements for cl-flet


From: akater
Subject: [PATCH] Some improvements for cl-flet
Date: Thu, 23 Sep 2021 22:37:33 +0000

Sorry, it took longer than I expected, partially due to my desire to “do
everything right” the first time.

* Issues with cl-flet
There are several issues with ~cl-flet~.

** No error on illegal function names
#+begin_src emacs-lisp :tangle no :results code :wrap example emacs-lisp
(macroexpand-1 `(cl-flet ((nil () t))
                  (funcall nil)))
#+end_src

#+RESULTS:
#+begin_example emacs-lisp
(let*
    ((--cl-nil--
      (cl-function
       (lambda nil t))))
  (progn
    (funcall nil)))
#+end_example

#+begin_src emacs-lisp :tangle no :results code :wrap example emacs-lisp
(macroexpand-1 `(cl-flet (((f) () t))
                  (funcall (f))))
#+end_src

#+RESULTS:
#+begin_example emacs-lisp
(let*
    ((--cl-\(f\)--
      (cl-function
       (lambda nil t))))
  (progn
    (funcall
     (f))))
#+end_example

** No error on malformed specs
#+begin_src emacs-lisp :tangle no :results code :wrap example emacs-lisp
(macroexpand-1 `(cl-flet ((f arglist yet-more))))
#+end_src

#+RESULTS:
#+begin_example emacs-lisp
(let*
    ((--cl-f--
      (cl-function
       (lambda arglist yet-more))))
  (progn))
#+end_example

#+begin_src emacs-lisp :tangle no :results code :wrap example emacs-lisp
(macroexpand-1 `(cl-flet ((f))))
#+end_src

#+RESULTS:
#+begin_example emacs-lisp
(let*
    ((--cl-f--
      (cl-function
       (lambda))))
  (progn))
#+end_example

** Incorrectly treated (setf ..) local functions This will attempt to
use a (non-existent, in this case) global function instead of the
generated local one:

#+begin_src emacs-lisp :tangle no :results code :wrap example emacs-lisp
(macroexpand-1
 `(cl-flet (((setf kar) (new cons) (setf (car cons) new)))
    (let ((cons (cons nil nil)))
      (setf (kar cons) t)
      cons)))
#+end_src

It would be nice to have a support for local setf functions, as in
Common Lisp.

** No warning on duplicated definitions
#+begin_src emacs-lisp :tangle no :results raw :wrap quote
(macroexpand-1 `(cl-flet ((f (x) (1+ x))
                          (f (x) x))
                  (f 42)))
(or (get-buffer "*Warnings*")
    "No warnings")
#+end_src

#+RESULTS:
#+begin_quote
No warnings
#+end_quote

** No warning on unused definitions
#+begin_src emacs-lisp :tangle no :results raw :wrap quote
(macroexpand-1 `(cl-flet ((f (x) x))
                  nil))
(or (get-buffer "*Warnings*")
    "No warnings")
#+end_src

#+RESULTS:
#+begin_quote
No warnings
#+end_quote

Unused definitions better be simply removed from the macroexpanded
code.

** No way to capture definitions present in the body
This is relevant for fixing an issue with ~cl--generic-lambda~: it needs
to record the used definitions; right now it parses with an expensive
and unreliable procedure.  The issue was discussed with Stefan Monnier
at https://lists.gnu.org/archive/html/emacs-devel/2021-09/msg00092.html
and we agreed it's best to alter ~cl-flet~ to record the actually
encountered definitions during macroexpansion.

* Patch overview
The attached patch fixes all of the above.

Note: Supporting local (setf ..) functions requires significantly more
code.  However, I think it's worth it and I hope it gets accepted.

Issue: I tried to alias ~cl--generic-with-memoization~ to
~cl--with-memoization~ but that failed at build time.  For a working
prototype, I just duplicate the definition.  The macro itself is
exactly the same.

Re: capture definitions present in the body,
I decided that simply collecting the actually encountered definitions
during macroexpansion and returning the list would be too ad-hoc so I
implemented expander that could evaluate arbitrary user-provided code
during macroexpansion.  Said code should be provided as 0-argument
lambdas; they are only executed once; the return value is then memoized
and reused during macroexpansion.  The table of memoized values is used
to report the “missing” and “duplicate” warnings.

In a nutshell,
#+begin_src emacs-lisp :tangle no :results code :wrap example emacs-lisp
(macroexpand-1 `(cl-flet ((f (x y) do f stuff)
                          (g expr))
                  body))
#+end_src

now amounts to
#+begin_src emacs-lisp :tangle no :results code :wrap example emacs-lisp
(cl--expand-flet macroexpand-all-environment body
  'g (lambda () (list nil expr))
  'f (lambda () (list (make-symbol (format "--cl-%s--" 'f))
                      `(cl-function (lambda (x y) do f stuff)))))
#+end_src

The first element of the returned list indicates whether the second
element should be bound to a local variable, or substituted as is.
There is no lighter alternative as type of expr may vary significantly,
particularly due to keeping the (func exp) syntax for c~l-flet~.

We could use cons rather than list for values but this way, return
values can be employed as let bindings as is.

It might be worth it to make lambdas accept arguments passed to local
functions in the body of ~cl-flet~ and make them run each time.  I found
this interface and the corresponding implementation too cumbersome and
not worth it in the absence of specific applications.  One possible such
application might be found in FIXME entry from =cl-generic.el=:
#+begin_example emacs-lisp
;; FIXME: Optimize the case where call-next-method is
;; only called with explicit arguments.
#+end_example

but I'm not sure I'm correct.

The new implementation of ~cl-flet~ can be macroexpanded with
~macroexpand-1~ just like the old one, so everything is compatible at
this level as well:

#+begin_src emacs-lisp :tangle no :results code :wrap example emacs-lisp
(cl-values
 (macroexpand-1 `(cl--flet/new ((f (x y) (+ x y)))
                   (f 1 2)))
 (macroexpand-1 `(cl--flet/old ((f (x y) (+ x y)))
                   (f 1 2))))
#+end_src

#+RESULTS:
#+begin_example emacs-lisp
((let*
     ((--cl-f--
       (cl-function
        (lambda
          (x y)
          (+ x y)))))
   (funcall --cl-f-- 1 2))
 (let*
     ((--cl-f--
       (cl-function
        (lambda
          (x y)
          (+ x y)))))
   (progn
     (funcall --cl-f-- 1 2))))
#+end_example

See the links below for ≈50 examples and tests in Org markup,
for general feel of what the patch does and what I've checked.

* External Links
- 
[[https://framagit.org/akater/elisp-cl-flet-improvement/-/raw/master/cl-flet-improvement.org?inline=true][for
 EWW and org-mode (recommended; see Tests section)]]
- 
[[https://framagit.org/akater/elisp-cl-flet-improvement/-/blob/master/cl-flet-improvement.org#L295][for
 other browsers]]

Attachment: signature.asc
Description: PGP signature


reply via email to

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