Use-package: proper way to define own lazy loaded commands

Created on 22 Dec 2017  Â·  13Comments  Â·  Source: jwiegley/use-package

for some packages i use, i use (usually small) interactive commands to get slightly different behaviours not offered by existing commands. one example would be this one:

  (defun w--counsel-recentf-other-window ()
    "Like `counsel-recentf', but opens the file in another window."
    (interactive)
    (let ((ivy-inhibit-action t))
      (find-file-other-window (counsel-recentf))))

preferably, i would keep these definitions inside the use-package stanza, e.g. in a :config block of a lazy loaded package:

(use-package counsel
  :defer t
  :config
  (defun ...))

whenever i bind such a command via :bind (or :general from general.el), use-package establishes autoloads for those, and everything works fine.

however, i do not always have key bindings to these commands, and in those cases no autoloads are being generated, making my command unusable until something causes use-package to load the package.

ideas / questions:

  • does it make sense to keep these custom interactive command defuns inside use-package (to me it feels right conceptually and visually)

  • what if use-package establishes an autoload for any (defun) encountered in a :config block?

  • perhaps these commands should go into :commands instead, which would then not only take symbols, but also actual (defun ...) forms

hopefully this all makes sense. thoughts welcome!

Most helpful comment

I'm against creating autoloads automagically for any defun within a config block. That feels much too special case, with the potential to cause unexpected behavior. Remember, use-package doesn't exist to turn Emacs Lisp into a declarative language, it only tries to eliminate repetition in commonly used idioms.

You are free, of course, to do something like this:

(defun use-package-autoloads/:config (name keyword args)
  (mapcar #'(lambda (x) (cons (cadr x) 'command))
          (cl-remove-if-not #'(lambda (x)
                                (and (consp x)
                                     (eq 'defun (car x))))
                            args)))

All 13 comments

does it make sense to keep these custom interactive command defuns inside use-package (to me it feels right conceptually and visually)

Yes, that's completely fine.

what if use-package establishes an autoload for any (defun) encountered in a :config block?

That's a bad idea. A user might not have all defuns in config designed to be interactive or auto loading.

perhaps these commands should go into :commands instead, which would then not only take symbols, but also actual (defun ...) forms

Yes, that works, and I believe a very good way to achieve what you want... Simply define the function in config. And if you like to autoload the package using that, use that function in :commands or :bind, etc.

Agreed with @kaushalmodi - I use :commands and :bind with symbol names of functions defined in :config plenty, and it works just fine.

yes, it works, but i find the required duplication annoying and error-prone, so i would love a cleaner more ‘dry’ way.

(use-package ...
  :commands
  cmd-x
  cmd-y
  :config
  (defun cmd-x ...)
  (defun cmd-y ...))

contains, at least imho, useless repetition.

instead, i would love something like this:

(use-package ...
  :commands
  (defun cmd-x ...)
  (defun cmd-y ...))

what if use-package establishes an autoload for any (defun) encountered in a :config block?

That's a bad idea. A user might not have all defuns in config designed to be interactive or auto loading.

can you elaborate? since i cannot think of any use case for which this would be problematic rather than convenient.

@wbolster I'm not sure I see this as useless repetition - it's no different than :commands with a package builtin, just the definition happens to be somewhere else (in the :config block, rather than source code). So this feels consistent the normal usage of :commands and is more clear to me than automatically autoloading defuns somehow.

I'm not arguing it wouldn't be more convenient to autoload any function you wanted with just a defun declaration (not arguing it is either), just stating why I don't think the current behavior is "useless".

ok, so my idea about autoloading still stands and as far as i can see there are no reasons against it.

so the request is basically "any (defun ...) inside a :config block should create an autoload for that function."

@jwiegley any thoughts perhaps?

I'm against creating autoloads automagically for any defun within a config block. That feels much too special case, with the potential to cause unexpected behavior. Remember, use-package doesn't exist to turn Emacs Lisp into a declarative language, it only tries to eliminate repetition in commonly used idioms.

You are free, of course, to do something like this:

(defun use-package-autoloads/:config (name keyword args)
  (mapcar #'(lambda (x) (cons (cadr x) 'command))
          (cl-remove-if-not #'(lambda (x)
                                (and (consp x)
                                     (eq 'defun (car x))))
                            args)))

@wbolster The reason against is the same reason why you wouldn't want to make all functions interactive.

Here's an example file in my config, where I have a bunch of functions -- some interactive, and good for autoloading, but many which are internal functions used by those interactive functions, advices, etc.

It doesn't make sense to unnecessarily make all those functions autoload eww (in that example).

ok, let me tighten the scope of my request a bit:

add autoloads for each (defun ...) in :config for which (commandp ...) holds?

this will only apply to interactive functions and seems to align very well with @jwiegley's earlier comment that

use-package doesn't exist to turn Emacs Lisp into a declarative language, it only tries to eliminate repetition in commonly used idioms.

the repetition in my earlier comment https://github.com/jwiegley/use-package/issues/607#issuecomment-353786811 is real and afaik a ‘commonly used idiom’ that use-package could improve upon!

@kaushalmodi i'm not sure i follow.

The reason against is the same reason why you wouldn't want to make all functions interactive.

that's not what i am advocating for at all.

and in your example, i wouldn't see how my suggestion to add autoloads for commands defined in :config would ever ‘autoload eww’? i guess there is some misunderstanding here?

@wbolster Is it a commonly-used idiom by most people? I don't recall ever seeing it before.

But you have the code now that you can add to the top of your init.el, which will give you just the behavior you're asking for. The argument is now whether everyone wants this, so as to make it a new default. This is where I disagree at present.

@jwiegley you may be right. if you have better solutions for writing a few package related commands that i write myself, _without repetitive code_, that would also be great. i am not aware of a better solution than the one i use, which is (defun ...) in :config and the name _repeated_ in :command, but i would love to learn if there is one!

@wbolster I meant using this code:

(defun use-package-autoloads/:config (name keyword args)
  (mapcar #'(lambda (x) (cons (cadr x) 'command))
          (cl-remove-if-not #'(lambda (x)
                                (and (consp x)
                                     (eq 'defun (car x))))
                            args)))
Was this page helpful?
0 / 5 - 0 ratings

Related issues

davep picture davep  Â·  6Comments

DamienCassou picture DamienCassou  Â·  8Comments

sheepduke picture sheepduke  Â·  3Comments

alphapapa picture alphapapa  Â·  14Comments

DrWaleedAYousef picture DrWaleedAYousef  Â·  11Comments