ragnese

joined 1 year ago
 

For example, let's say I were to change backward-delete-char-untabify-method for prog-mode buffers. Naively, I'd write something like,

(add-hook 'prog-mode-hook (lambda ()
                       (setq-local backward-delete-char-untabify-method 'hungry)))

but the documentation recommends against using lambdas in add-hook calls (which makes sense). I can, of course, just make a named function instead of a lambda and pass that to add-hooks. But, rather than do that, is there any other option for setting variables automatically for modes besides a hook like this?

Also, as a bonus question, if I wanted to also do something like enable show-paren-mode for all prog-mode buffers, which of the following do you consider "better" (assume that this is not something that is likely to change frequently):

;; option 1
(defun my-prog-mode-settings ()
  (setq-local backward-delete-char-untabify-method 'hungry))

(add-hook 'prog-mode-hook #'my-prog-mode-settings)
(add-hook 'prog-mode-hook #'show-paren-mode)

;; option 2
(defun my-prog-mode-settings ()
  (setq-local backward-delete-char-untabify-method 'hungry)
  (show-paren-mode))

(add-hook 'prog-mode-hook #'my-prog-mode-settings)

Obviously, I realize that it's not really important, but I feel like bike-shedding this morning.

[–] ragnese@alien.top 1 points 11 months ago

As for :init, whatever you put in there you can just put outside of use-packge right?

99% of the time, yes. As far as I know there are three "lifecycle hooks": prelude, init, config. Prelude is evaluated when the use-package form is encountered no matter what (even if it's :disabled, etc); then init is evaluated if the package is not disabled via :requires, :disabled, :if, etc; then config is essentially the same as with-eval-after-load.

[–] ragnese@alien.top 1 points 11 months ago (1 children)

People seem to be missing the point that no conceivable package loading tool — use-package, general, straight, by-hand gnarly lisp using eval-after-load, etc., etc. — will solve the original problem as stated. You want a binding to appear, tying project to magit. That binding is specified at the top level in one of magit's many lisp files. You want this binding to appear without loading magit. There is no approach which will accomplish this, other than excerpting that binding yourself, as you've done in the "clever/hacky" solution

Yes, agreed. My contention is not with the inherent complexity of the situation. Rather, my issue is related to this last part of your paragraph,

though I'd put it in the project :config stanza for better organization

That's the whole problem with using use-package forms for configuration! use-package is centered around a single feature at a time, whereas many configurations involve tying multiple features together. In this case, it doesn't matter if I put the configuration in project's use-package form or in magit's use-package form--in either case, one of the forms will be misleading because it will seem like all of the configurations related to that package is in that form, but it won't be true because something about it is being configured in the other package's form.

That's not the only thing, either. Another example is completions. I use vertico and corfu, but they tie in closely with a lot of built-in Emacs completion stuff, so I have the following settings,

(customize-set-variable 'read-extended-command-predicate #'command-completion-default-include-p)
(setq completion-category-overrides '((file (styles partial-completion))))
(setq completion-styles '(flex basic))

those settings are not part of the vertico package, but they're being set because I'm using vertico and want it to behave a certain way. So, do they go in the vertico use-package, or do they go in an emacs or simple.el use-package, or do they just go at the top level somewhere?

The truth is that we don't actually configure packages in isolation, which is why I feel like use-package kind of imposes a structure that appears to make sense in a lot of cases, but does NOT actually make sense in the general case.

[–] ragnese@alien.top 1 points 11 months ago (2 children)

That's also true of the :map and :hook forms, no? They all use magical syntax sugar that is meaningless outside of the macro. Luckily, :init and :config basically just wrap their contents in a (progn) with some error handling, etc.

[–] ragnese@alien.top 1 points 11 months ago (2 children)

I think your problems are due to not understanding how Emacs works under the hood. To me, the only problem with use-package is that it hides those details and makes people learn a DSL that use-package is, instead of learning simpler mechanisms of with-eval-after-load and hooks which are used to implement the functionality of use-package under the hood. In past years I have seen many questions here on Reddit and on SX related to use-package because people don't understand Emacs under the hood. But that is about it; I see no other drawbacks than that.

As a software developer, if you have to understand (exactly) what is happening under the hood of an abstraction in order to use it correctly, then it's not a good abstraction. The entire point of abstraction is to allow us to think on a higher level, and I would say that use-package fails that test because it has a ton of nuance and subtle self-interactions that requires me to more-or-less just macro-expand all of my declarations to see what they are actually doing.

The fact that so many people struggle with it gives credence to this as well. Frankly, Emacs is complex and complicated. It has lots of legacy baggage and idiosyncrasies, so I would expect people to get their with-eval-after-loads wrong or their keybinding syntaxes wrong. But, use-package seems to hurt as much as it helps.

[–] ragnese@alien.top 1 points 11 months ago

EDIT: In this particular case (magit and project), however, I don't understand the problem. magit-project-status is autoloaded, so shouldn't it be available irrespective of the relative load orders of project and magit?

Correct. The problem is that magit will add itself to project.el's maps/commands after magit is loaded. So, if I open Emacs and want to just right into a git project, I might call project-switch-project, select a project and not have the Magit command available because magit hasn't loaded yet and therefore hasn't injected itself.

[–] ragnese@alien.top 1 points 11 months ago

Can you give me an example where you use :after? Isn't it pretty tricky to use :after when also setting use-package-always-defer?

[–] ragnese@alien.top 1 points 11 months ago

Your life story wasn't in the way until you put it there.

I find that adding my "credentials" or background sometimes helps when posting questions or discussions on Reddit. If I had posted that I found use-package's handling of autoloading a little confusing without any context, I'm fairly sure that I would get a bunch of replies explaining how it works, rather than a discussion of whether the way it works is good.

But, basically, I agree with you that eval-after-load is about half of what I wanted from use-package--the other half being a way to declare which installable packages I want available.

 

Context:

I've used Emacs off and on for many years at this point. Throughout that time there have been periods where I really leaned in to it and tried to use it for everything, and there have been periods where I only used it for org and/or magit, etc. I've learned lots of things about it and I've forgotten lots of things about it, but I've never been what I would call an "expert" or even a "power user". So, when I feel like something isn't working well in Emacs, I almost always default to the assumption that I'm doing something wrong or misunderstanding something, etc.

So, it very well may be that I'm wrong/crazy in my recent conclusion that use-package might not be the ideal abstraction for managing Emacs packages.

With that out of the way, I'll say that when I first saw use-package, I thought it was amazing. But, in the years that I've been using use-package, I never feel like my init file is "right". Now, I'm starting to think that maybe it's use-package that's wrong and not me (insert Simpsons principal Skinner meme).

I don't know how best to articulate what I mean by use-package being a "wrong abstraction", but I'll try by listing some examples and thoughts.

Autoloads

First of all, I feel like the way autoloads are handled with use-package is too mistake-prone. Libraries/packages typically define their own autoloads, but the use-package default is to eagerly load the package. I understand that installing a library via package.el, etc will process the autoloads for us and that manually/locally installed packages get no such benefit.

But, if we're using use-package to also manage installing the packages for us (:ensure t), then why shouldn't it know about the autoloads already and automagically imply a :defer t by default?

So, by default, we have to remember to either add :defer t or we have to remember that setting our own hooks, bindings, or commands will create autoloads for us.

I know that you can configure use-package to behave as though :defer t is set by default, but that's just broken for packages that don't have any autoloads.

It feels like maybe use-package is doing too many things. Maybe it was actually more correct in the old days to separate the installation, configuration, and actual loading of packages, rather than trying to do all three in one API.

Configuration that depends on multiple packages is ugly/inconsistent

Many packages are fairly standalone, so you can just do,

(use-package foo
    :defer t
    :config
    (setq foo-variable t))

and it's clean and beautiful. But, sometimes we have configuration that is across multiple packages. A real-world example for me is magit and project.el. Magit actually provides project.el integration, wherein it adds magit commands to the project-switch-commands and the project-prefix-map. That's great, but it will only run if/when the magit package is loaded.

So, my first guess at using use-package with magit was this,

(use-package magit
    :ensure t
    :defer t
    :config
    (setq magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1))

which seems reasonable since I know that magit defines its own autoloads. However, I was confused when I'd be using Emacs and the project.el switch choices showed a magit option sometimes.

I eventually realized what was going on and realized that the solution was to immediately load magit,

(use-package magit
    :ensure t
    :config
    (setq magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1))

but that kind of sucks because there's no reason to load magit before I actually want to use it for anything. So, what we can do instead is to implement the project.el integration ourselves. It's really just two commands:

(define-key project-prefix-map "m" #'magit-project-status)
(add-to-list 'project-switch-commands '(magit-project-status "Magit") t)

But, the question is: Where do we put these, and when should they be evaluated? I think that just referring to a function symbol doesn't trigger autoloading, so I believe these configurations should happen after project.el is loaded, and that it doesn't matter if magit is loaded or not yet.

Since, project.el is built-in to Emacs, it's probably most reasonable to do that config in the magit use-package form, but what if project.el were another third-party package that had its own use-package form? Would we add the config in the project use-package form, or in the magit use-package form? Or, we could do something clever/hacky,

(use-package emacs
    :after project
    :requires magit
    :config
    (define-key project-prefix-map "m" #'magit-project-status)
    (add-to-list 'project-switch-commands '(magit-project-status "Magit") t))

But, if we do this a lot, then it feels like our init.el is getting just as disorganized as it was before use-package.

Conclusion

This is too rambly already. I think the point is that I'm becoming less convinced that installing/updating packages, loading them, and configuring them at the same time is the right way to think about it.

Obviously, if you know what you're doing, you can use use-package to great success. But, I think my contention is that I've been familiar with Emacs for a long time, I'm a professional software developer, and I still make mistakes when editing my init file. Either I'm a little dim or the tooling here is hard to use correctly.

Am I the only one?