~pkal/public-inbox

7 2

setup.el: :repeatable doesn't respect list arguments

Details
Message ID
<87frvii7l8.fsf@ergo>
DKIM signature
pass
Download raw message
I'm forwarding the following message because I believe it wasn't
delivered (I've been having issues with emails appearing as sent but not
being delivered).

-------------------- Start of forwarded message --------------------
From: João Pedro <jpedrodeamorim@gmail.com>
To: ~pkal/public-inbox@lists.sr.ht
Subject: setup.el: :repeatable doesn't respect list arguments
Date: Thu, 18 Apr 2024 20:09:14 -0300

Greetings,

I'm trying to define a local macro to only install packages after some
features have been loaded, here's the code for it:

    (setup-define :package-after
      (lambda (features package)
        (let* ((body `(message ,(format "%s" package))))
          (dolist (feature (nreverse (listify features)))
            (setq body `(with-eval-after-load ',feature ,body)))
          body))
      :documentation "Install PACKAGE after FEATURES are loaded.
    Just wrap `\(:package FEATURE\)' in a `with-eval-after-load'
    block for each feature in FEATURES. FEATURES may be either a
    symbol or a list of symbols."
      :debug '([&or ([&rest sexp] sexp)] symbolp)
      :repeatable '(1 . 1)
      :shorthand #'caddr)

But I'm running into a problem, reproducible with `emacs -q'. The first
argument, FEATURES, may be a list, in which case the package is only
installed if all of the features in the list have been loaded. (listify
is just a shorthad for (if (listp features) features (list features)))).
The issue arises when trying to repeat this macro to install various
packages:

    (setup (:package-after (feat-1 feat-2 ... feat-n) pkg-1 pkg2 ... pkg-n))

will be executed as

    (progn
      (:package-after (feat-1 feat-2 ... feat-n) pkg-1)
      (:package-after feat-1 pkg-2)
      ...
      (:package-after feat-1 pkg-n))

I didn't paste the full macroexpand for brevity's sake.

Also, not really related to this, but Flymake is incorrectly flagging
some warnings on user-defined local macros. Back to the :package-after
example, if I had e.g.

    (setup (:package-after feat-1 pkg-1))

it would complain that :package-after is being called as a function,
plus it would flag all symbols as not known to be defined. That happens
with any user defined macro. Plus

    (setup (:package-after feat-1 pkg-1)
      (:hook #'hook-into-pkg-1))

would complain, on :hook, that it cannot deduce hook from context, even
though it evaluates and macroexpands correctly. Do I need to add
something to `elisp-flymake-byte-compile-load-path'?

Cheers,

-- 
João Pedro de A. Paula
IT bachelors at Universidade Federal do Rio Grande do Norte (UFRN)
-------------------- End of forwarded message --------------------

-- 
João Pedro de A. Paula
IT bachelors at Universidade Federal do Rio Grande do Norte (UFRN)
Details
Message ID
<87r0erg2ye.fsf@ergo>
In-Reply-To
<87frvii7l8.fsf@ergo> (view parent)
DKIM signature
pass
Download raw message
Hi Philip,

As you suggested in your personal blog[0], I'm pinging you with regards
to this after a week :D

[0] https://amodernist.com/#email

Cheers,

Em quinta, 18/04/2024 às 20:17 (-03), João Pedro <jpedrodeamorim@gmail.com> escreveu:

> Greetings,
>
> I'm trying to define a local macro to only install packages after some
> features have been loaded, here's the code for it:
>
>     (setup-define :package-after
>       (lambda (features package)
>         (let* ((body `(message ,(format "%s" package))))
>           (dolist (feature (nreverse (listify features)))
>             (setq body `(with-eval-after-load ',feature ,body)))
>           body))
>       :documentation "Install PACKAGE after FEATURES are loaded.
>     Just wrap `\(:package FEATURE\)' in a `with-eval-after-load'
>     block for each feature in FEATURES. FEATURES may be either a
>     symbol or a list of symbols."
>       :debug '([&or ([&rest sexp] sexp)] symbolp)
>       :repeatable '(1 . 1)
>       :shorthand #'caddr)
>
> But I'm running into a problem, reproducible with `emacs -q'. The first
> argument, FEATURES, may be a list, in which case the package is only
> installed if all of the features in the list have been loaded. (listify
> is just a shorthad for (if (listp features) features (list features)))).
> The issue arises when trying to repeat this macro to install various
> packages:
>
>     (setup (:package-after (feat-1 feat-2 ... feat-n) pkg-1 pkg2 ... pkg-n))
>
> will be executed as
>
>     (progn
>       (:package-after (feat-1 feat-2 ... feat-n) pkg-1)
>       (:package-after feat-1 pkg-2)
>       ...
>       (:package-after feat-1 pkg-n))
>
> I didn't paste the full macroexpand for brevity's sake.
>
> Also, not really related to this, but Flymake is incorrectly flagging
> some warnings on user-defined local macros. Back to the :package-after
> example, if I had e.g.
>
>     (setup (:package-after feat-1 pkg-1))
>
> it would complain that :package-after is being called as a function,
> plus it would flag all symbols as not known to be defined. That happens
> with any user defined macro. Plus
>
>     (setup (:package-after feat-1 pkg-1)
>       (:hook #'hook-into-pkg-1))
>
> would complain, on :hook, that it cannot deduce hook from context, even
> though it evaluates and macroexpands correctly. Do I need to add
> something to `elisp-flymake-byte-compile-load-path'?
>
> Cheers,
>
> -- 
> João Pedro de A. Paula
> IT bachelors at Universidade Federal do Rio Grande do Norte (UFRN)

-- 
João Pedro de A. Paula
IT bachelors at Universidade Federal do Rio Grande do Norte (UFRN)
Details
Message ID
<87mspfl3cv.fsf@posteo.net>
In-Reply-To
<87r0erg2ye.fsf@ergo> (view parent)
DKIM signature
pass
Download raw message
João Pedro <jpedrodeamorim@gmail.com> writes:

> Hi Philip,
>
> As you suggested in your personal blog[0], I'm pinging you with regards
> to this after a week :D

My apologies, I had convinced myself that I had already responded to
your message, but it appears all I did was prepare a message in my mind.
Thank you for the reminder, my backlog is currently horrible :/

> [0] https://amodernist.com/#email
>
> Cheers,
>
> Em quinta, 18/04/2024 às 20:17 (-03), João Pedro <jpedrodeamorim@gmail.com> escreveu:
>
>> Greetings,
>>
>> I'm trying to define a local macro to only install packages after some
>> features have been loaded, here's the code for it:
>>
>>     (setup-define :package-after
>>       (lambda (features package)
>>         (let* ((body `(message ,(format "%s" package))))
>>           (dolist (feature (nreverse (listify features)))
>>             (setq body `(with-eval-after-load ',feature ,body)))
>>           body))
>>       :documentation "Install PACKAGE after FEATURES are loaded.
>>     Just wrap `\(:package FEATURE\)' in a `with-eval-after-load'
>>     block for each feature in FEATURES. FEATURES may be either a
>>     symbol or a list of symbols."
>>       :debug '([&or ([&rest sexp] sexp)] symbolp)
>>       :repeatable '(1 . 1)
>>       :shorthand #'caddr)
>>
>> But I'm running into a problem, reproducible with `emacs -q'. The first
>> argument, FEATURES, may be a list, in which case the package is only
>> installed if all of the features in the list have been loaded. 

So the (format "%s" package) above is just a placeholder?
                                                                  
>>                                                                (listify
>> is just a shorthad for (if (listp features) features (list features)))).

That's `ensure-list' (Emacs 28.1+) btw.

>> The issue arises when trying to repeat this macro to install various
>> packages:
>>
>>     (setup (:package-after (feat-1 feat-2 ... feat-n) pkg-1 pkg2 ... pkg-n))
>>
>> will be executed as
>>
>>     (progn
>>       (:package-after (feat-1 feat-2 ... feat-n) pkg-1)
>>       (:package-after feat-1 pkg-2)
>>       ...
>>       (:package-after feat-1 pkg-n))
>>
>> I didn't paste the full macroexpand for brevity's sake.

And I assume you want the expansion to be equivalent to 

  (:package-after (feat-1 feat-2 ... feat-n) pkg-2)

right?

I tried simplifying the code, but it didn't cause any issues:

--8<---------------cut here---------------start------------->8---
(setup-define :test-two-arg-repeatable
  (lambda (one two) (list :one one :two two))
  :repeatable '(1 . 1))

(macroexpand-all
 '(setup _ (:test-two-arg-repeatable eins zwei drei)))
;; (progn (:one eins :two zwei) (:one eins :two drei))

(macroexpand-all
 '(setup _ (:test-two-arg-repeatable (eins uno) zwei drei)))
;; (progn (:one (eins uno) :two zwei) (:one (eins uno) :two drei))
--8<---------------cut here---------------end--------------->8---

What is probably causing the issue here is that nreverse is destructive,
and your listify doesn't copy lists, so you are passing the AST
S-expression and modifying that.  Since the first element (or the
cons-cell of the first element) becomes the last,

  (let ((l '(1 2 3))) (nreverse l) l) ;=> (1)

which looks like what you are experiencing.  So without having tested it
myself, I'd replace the nreverse with reverse or modify listify.

>> Also, not really related to this, but Flymake is incorrectly flagging
>> some warnings on user-defined local macros. Back to the :package-after
>> example, if I had e.g.
>>
>>     (setup (:package-after feat-1 pkg-1))
>>
>> it would complain that :package-after is being called as a function,
>> plus it would flag all symbols as not known to be defined. That happens
>> with any user defined macro. Plus
>>
>>     (setup (:package-after feat-1 pkg-1)
>>       (:hook #'hook-into-pkg-1))
>>
>> would complain, on :hook, that it cannot deduce hook from context, even
>> though it evaluates and macroexpands correctly. Do I need to add
>> something to `elisp-flymake-byte-compile-load-path'?

That might be one solution, what I do is just add

  (eval-when-compile (require 'setup))

to the beginning of my init.

>> Cheers,
>>
>> -- 
>> João Pedro de A. Paula
>> IT bachelors at Universidade Federal do Rio Grande do Norte (UFRN)

-- 
	Philip Kaludercic on barbet
Details
Message ID
<87il01ffmj.fsf@ergo>
In-Reply-To
<87mspfl3cv.fsf@posteo.net> (view parent)
DKIM signature
pass
Download raw message
Em sábado, 27/04/2024 às 06:35 (GMT), Philip Kaludercic <philipk@posteo.net> escreveu:

> My apologies, I had convinced myself that I had already responded to
> your message, but it appears all I did was prepare a message in my mind.
> Thank you for the reminder, my backlog is currently horrible :/

No worries! Thanks for taking the time to reply.

> So the (format "%s" package) above is just a placeholder?

Yeah, sorry about that. It should've been `(:package ,package) instead.

> That's `ensure-list' (Emacs 28.1+) btw.

Oh cool! I have been using this listify for a while now (and setup.el
also has a bunch of (if (listp foo) foo (list foo))), guess I'll just
have to create an alias.

> And I assume you want the expansion to be equivalent to 
>
>   (:package-after (feat-1 feat-2 ... feat-n) pkg-2)
>
> right?
>
> I tried simplifying the code, but it didn't cause any issues:
>
> --8<---------------cut here---------------start------------->8---
> (setup-define :test-two-arg-repeatable
>   (lambda (one two) (list :one one :two two))
>   :repeatable '(1 . 1))
>
> (macroexpand-all
>  '(setup _ (:test-two-arg-repeatable eins zwei drei)))
> ;; (progn (:one eins :two zwei) (:one eins :two drei))
>
> (macroexpand-all
>  '(setup _ (:test-two-arg-repeatable (eins uno) zwei drei)))
> ;; (progn (:one (eins uno) :two zwei) (:one (eins uno) :two drei))
> --8<---------------cut here---------------end--------------->8---

Should've tried that before emailing, sorry.

> What is probably causing the issue here is that nreverse is destructive,
> and your listify doesn't copy lists, so you are passing the AST
> S-expression and modifying that.  Since the first element (or the
> cons-cell of the first element) becomes the last,
>
>   (let ((l '(1 2 3))) (nreverse l) l) ;=> (1)
>
> which looks like what you are experiencing.  So without having tested it
> myself, I'd replace the nreverse with reverse or modify listify.

Yeah, that is the case indeed. I always mix nreverse and reverse up.
Sorry for bothering you with this and thank you for pointing me in the
right direction!

> That might be one solution,

But what would I add there? The file where I'm currently defining the
local macros? That doesn't seem to be the case 'cause putting setup code
with local macros right before its definition, e.g.

(setup-define :foo (lambda ()))

(setup bar (:foo))

it still complains that "`:foo' is called as a function".

> what I do is just add
>
>   (eval-when-compile (require 'setup))
>
> to the beginning of my init.

I go a step further and just put (require 'setup). Using
`eval-when-compile' adds warning to any setup function that it might not
be defined at runtime.

-- 
João Pedro de A. Paula
IT bachelors at Universidade Federal do Rio Grande do Norte (UFRN)
Details
Message ID
<87zftcn2l2.fsf@posteo.net>
In-Reply-To
<87il01ffmj.fsf@ergo> (view parent)
DKIM signature
pass
Download raw message
João Pedro <jpedrodeamorim@gmail.com> writes:

> Em sábado, 27/04/2024 às 06:35 (GMT), Philip Kaludercic <philipk@posteo.net> escreveu:
>
>> My apologies, I had convinced myself that I had already responded to
>> your message, but it appears all I did was prepare a message in my mind.
>> Thank you for the reminder, my backlog is currently horrible :/
>
> No worries! Thanks for taking the time to reply.
>
>> So the (format "%s" package) above is just a placeholder?
>
> Yeah, sorry about that. It should've been `(:package ,package) instead.
>
>> That's `ensure-list' (Emacs 28.1+) btw.
>
> Oh cool! I have been using this listify for a while now (and setup.el
> also has a bunch of (if (listp foo) foo (list foo))), guess I'll just
> have to create an alias.

That is because I didn't want to raise setup.el's minimum version to
28.1 or add Compat as a dependency.

>> And I assume you want the expansion to be equivalent to 
>>
>>   (:package-after (feat-1 feat-2 ... feat-n) pkg-2)
>>
>> right?
>>
>> I tried simplifying the code, but it didn't cause any issues:
>>
>> --8<---------------cut here---------------start------------->8---
>> (setup-define :test-two-arg-repeatable
>>   (lambda (one two) (list :one one :two two))
>>   :repeatable '(1 . 1))
>>
>> (macroexpand-all
>>  '(setup _ (:test-two-arg-repeatable eins zwei drei)))
>> ;; (progn (:one eins :two zwei) (:one eins :two drei))
>>
>> (macroexpand-all
>>  '(setup _ (:test-two-arg-repeatable (eins uno) zwei drei)))
>> ;; (progn (:one (eins uno) :two zwei) (:one (eins uno) :two drei))
>> --8<---------------cut here---------------end--------------->8---
>
> Should've tried that before emailing, sorry.

No problem, I'm always happy to receive emails like these, because then
at least I know that someone is using setup.el ^^

>> What is probably causing the issue here is that nreverse is destructive,
>> and your listify doesn't copy lists, so you are passing the AST
>> S-expression and modifying that.  Since the first element (or the
>> cons-cell of the first element) becomes the last,
>>
>>   (let ((l '(1 2 3))) (nreverse l) l) ;=> (1)
>>
>> which looks like what you are experiencing.  So without having tested it
>> myself, I'd replace the nreverse with reverse or modify listify.
>
> Yeah, that is the case indeed. I always mix nreverse and reverse up.
> Sorry for bothering you with this and thank you for pointing me in the
> right direction!

1+

It is an intermediate mistake that one has to make once.

>> That might be one solution,
>
> But what would I add there? The file where I'm currently defining the
> local macros? That doesn't seem to be the case 'cause putting setup code
> with local macros right before its definition, e.g.
>
> (setup-define :foo (lambda ()))
>
> (setup bar (:foo))
>
> it still complains that "`:foo' is called as a function".

Right, because `setup-define' is just a function that the byte-compiler
does not evaluate, hence it never defines the local macros....

>> what I do is just add
>>
>>   (eval-when-compile (require 'setup))
>>
>> to the beginning of my init.
>
> I go a step further and just put (require 'setup). Using
> `eval-when-compile' adds warning to any setup function that it might not
> be defined at runtime.

To be honest, I actually have this:

(eval-when-compile
  (when after-init-time
    (package-initialize))
  (require 'setup nil t))

but it all really isn't that nice.  I'll have sit down and figure out
how to improve the situation upstream inside of setup.el, instead of
having everyone to hack something up themselves in their configurations.

-- 
	Philip Kaludercic on peregrine
Details
Message ID
<87ttji3510.fsf@ergo>
In-Reply-To
<87zftcn2l2.fsf@posteo.net> (view parent)
DKIM signature
pass
Download raw message
> No problem, I'm always happy to receive emails like these, because then
> at least I know that someone is using setup.el ^^

I've been meaning to try setup.el for a long time now. Most of the local
macros I defined for setup.el were macros I already had on my config. I
tried use-package for a while but it felt too magic, so I just came up
with my own wrapper to `with-eval-after-load' and a function to install
a package if it isn't installed and went from there. Couple years pass
and I start to realize I have created a bunch of other macros here and
there to do other things, but they were all over the place. That's where
setup.el comes in! It allows me put all of my configuration for a single
library in the same place, with some neat macros to make things cleaner!
I guess it might not be everyone's cup of tea, but it fit like a glove
in how I organize my configuration.

> To be honest, I actually have this:
>
> (eval-when-compile
>   (when after-init-time
>     (package-initialize))
>   (require 'setup nil t))

Huh, why do you initialize package that way? I've been wondering how I
should that to optimize startup time and not have to load unnecessary
things.

> Right, because `setup-define' is just a function that the byte-compiler
> does not evaluate, hence it never defines the local macros....
>
> but it all really isn't that nice.  I'll have sit down and figure out
> how to improve the situation upstream inside of setup.el, instead of
> having everyone to hack something up themselves in their configurations.

I'm always lost on how the byte compiler and Flymake interact/work. But
I haven't dug much deep there. I get a bunch of warnings on my config
that shouldn't be there, like e.g. telling me that a function might not
be defined when it is wrapped on

`(with-eval-after-load 'feature-that-loads-function ...)'

Let me know if I can be of assistance in that regard. I like to keep my
config free of byte compiler warnings when possible!

-- 
João Pedro de A. Paula
IT bachelors at Universidade Federal do Rio Grande do Norte (UFRN)
Details
Message ID
<87v83u4qvr.fsf@posteo.net>
In-Reply-To
<87ttji3510.fsf@ergo> (view parent)
DKIM signature
pass
Download raw message
João Pedro <jpedrodeamorim@gmail.com> writes:

[...]

>> To be honest, I actually have this:
>>
>> (eval-when-compile
>>   (when after-init-time
>>     (package-initialize))
>>   (require 'setup nil t))
>
> Huh, why do you initialize package that way? I've been wondering how I
> should that to optimize startup time and not have to load unnecessary
> things.

Apparently it doesn't make a change if I simplify it to the snippet I
have above, and all I have in my init-git is a brief comment on
something about accelerating the startup time.

>> Right, because `setup-define' is just a function that the byte-compiler
>> does not evaluate, hence it never defines the local macros....
>>
>> but it all really isn't that nice.  I'll have sit down and figure out
>> how to improve the situation upstream inside of setup.el, instead of
>> having everyone to hack something up themselves in their configurations.
>
> I'm always lost on how the byte compiler and Flymake interact/work. But
> I haven't dug much deep there. I get a bunch of warnings on my config
> that shouldn't be there, like e.g. telling me that a function might not
> be defined when it is wrapped on

I think one would have to study `elisp-flymake-byte-compile' and related
functions in greater detail to understand that

> `(with-eval-after-load 'feature-that-loads-function ...)'
>
> Let me know if I can be of assistance in that regard. I like to keep my
> config free of byte compiler warnings when possible!

Will do, but as I said I first have to fine the time and motivation to
work on this in the first place.  It is not really a pressing issue, and
I have to admit that my enthusiasm wrt. setup.el is not really at an
all-time-high (though I still prefer it to use-package).

-- 
	Philip Kaludercic on peregrine
Details
Message ID
<87zft314no.fsf@ergo>
In-Reply-To
<87v83u4qvr.fsf@posteo.net> (view parent)
DKIM signature
pass
Download raw message
Em sexta, 03/05/2024 às 19:50 (GMT), Philip Kaludercic <philipk@posteo.net> escreveu:

> I think one would have to study `elisp-flymake-byte-compile' and related
> functions in greater detail to understand that

I've skimmed through it a couple of times, but the byte compilation is
still bit cryptic in what it does.

> Will do, but as I said I first have to fine the time and motivation to
> work on this in the first place.  It is not really a pressing issue, and
> I have to admit that my enthusiasm wrt. setup.el is not really at an
> all-time-high (though I still prefer it to use-package).

That's fine, it really is just annoying warnings TBH. I've picked up
setup.el very recently, and I'm slowly but surely rewriting my whole
config with it, so my enthusiasm wrt. it *is* at an all-time-high, so I
might end up hacking it and proposing changes with patches attached. It
is definitely a much better cleaner solution than use-package, and much
easier to debug (i.e. macroexpand) and understand what's going on, which
is probably its biggest selling point to me.

-- 
João Pedro de A. Paula
IT bachelors at Universidade Federal do Rio Grande do Norte (UFRN)
Reply to thread Export thread (mbox)