conao3 / leaf.el

Flexible, declarative, and modern init.el package configuration
GNU General Public License v3.0
505 stars 28 forks source link

What is difference between use-package and leaf? #257

Open arkhan opened 5 years ago

arkhan commented 5 years ago

Regards,

I found this package and it seemed interesting, but I had a question, what are the differences or advantages over use-package?

Great job

conao3 commented 5 years ago

Good question. Now, I've been using use-package for a long time. Have you ever been stressed by use-package?

For example, can you add a keyword like :doc in leaf, which is the "Do nothing" keyword to use-package? When I tried it, I couldn't do it. Please try it. This is the first step in understanding use-package.

That in leaf's implementation is very easy. https://github.com/conao3/leaf.el/blob/da616cda1418ba4ea15d75944e5c52fd7cface6e/leaf.el#L212 It simply returns the previous value as its own value. If you're not familiar with recursive functions, it might be difficult to read, but it's an important concept for programmers, even if it's not in lisp context.

The key difference for end users is probably the difference between the :bind and :custom keywords. The :bind keyword of use-package breaks indentation when there is no global assignment. I knew that, so I was able to design a receipt format that would not break indents.

(use-package term
  :bind (("C-c t" . term)
         :map term-mode-map
         ("M-p" . term-send-up)         ; good indent
         ("M-n" . term-send-down)))

(use-package term
  :bind (:map term-mode-map
              ("M-p" . term-send-up)    ; indentation broken
              ("M-n" . term-send-down)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(leaf term
  :bind (("C-c t" . term)
         (term-mode-map                 ; or keyword :term-mode-map
          ("M-p" . term-send-up)
          ("M-n" . term-send-down))))

(leaf term
  :bind ((term-mode-map                 ; or keyword :term-mode-map
          ("M-p" . term-send-up)
          ("M-n" . term-send-down))))

The :custom keyword in use-package is disproportionate to keywords that accept dotted pairs, such as :mode and :hook etc. How many people set custom-set-variables's third argument? The leaf decided that it was more important to harmonize the whole than to consider the unusual scene.

(p (use-package org
     :bind (("M-o o c" . org-capture)
            ("M-o o a" . org-agenda)
            ("M-o o l" . org-store-link))))        ;  expect dot-pair
;; => (progn
;;      (unless (fboundp 'org-capture) (autoload #'org-capture "org" nil t))
;;      (unless (fboundp 'org-agenda) (autoload #'org-agenda "org" nil t))
;;      (unless (fboundp 'org-store-link) (autoload #'org-store-link "org" nil t))
;;      (bind-keys :package org
;;                 ("M-o o c" . org-capture)
;;                 ("M-o o a" . org-agenda)
;;                 ("M-o o l" . org-store-link)))

(p (use-package real-auto-save
     :hook (find-file . real-auto-save-mode)))     ; of cource expect dot-pair
;; => (progn
;;     (unless (fboundp 'real-auto-save-mode) (autoload #'real-auto-save-mode "real-auto-save" nil t))
;;     (add-hook 'find-file-hook #'real-auto-save-mode))

(p (use-package real-auto-save
     :custom ((real-auto-save-interval . 0.3))))   ; error when passing pair!??
;; => Debugger entered--Lisp error: (wrong-type-argument listp 0.3)

(p (use-package real-auto-save
     :custom ((real-auto-save-interval 0.3))))     ; expect list!??
;; => (progn
;;      (customize-set-variable 'real-auto-save-interval 0.3 "Customized with use-package real-auto-save")
;;      (require 'real-auto-save nil nil))
(p (leaf org
     :bind (("M-o o c" . org-capture)
            ("M-o o a" . org-agenda)
            ("M-o o l" . org-store-link))))         ; expect dot-pair
;; => (prog1 'org
;;      (autoload #'org-capture "org" nil t)
;;      (autoload #'org-agenda "org" nil t)
;;      (autoload #'org-store-link "org" nil t)
;;      (leaf-keys
;;       (("M-o o c" . org-capture)
;;        ("M-o o a" . org-agenda)
;;        ("M-o o l" . org-store-link))))

(p (leaf real-auto-save
     :hook (find-file-hook . real-auto-save-mode))) ; expect dot-pair
;; => (prog1 'real-auto-save
;;      (autoload #'real-auto-save-mode "real-auto-save" nil t)
;;      (add-hook 'find-file-hook #'real-auto-save-mode))

(p (leaf real-auto-save
     :custom ((real-auto-save-interval . 0.3))))    ; expect dot-pair
;; => (prog1 'real-auto-save
;;      (custom-set-variables
;;       '(real-auto-save-interval 0.3 "Customized with leaf in real-auto-save block")))

Finally, the clean internal implementation of leaf may certainly be meaningless to the end user. However, the ease of internal implementation makes it easier for developers to benefit from additional keywords. In fact, it supports more keywords than use-package and you don't have to be me to do that. Because it is easy.

use-package-keywords                (leaf-available-keywords)
;;=> (:disabled                     ;;=> (:disabled
;;    :load-path                    ;;    :convert-defaults
;;    :requires                     ;;    :leaf-protect
;;    :defines                      ;;    :load-path
;;    :functions                    ;;    :load-path*
;;    :preface                      ;;    :leaf-autoload
;;    :if                           ;;    :defun
;;    :when                         ;;    :defvar
;;    :unless                       ;;    :leaf-defun
;;    :no-require                   ;;    :leaf-defvar
;;    :catch                        ;;    :preface
;;    :after                        ;;    :when
;;    :custom                       ;;    :unless
;;    :custom-face                  ;;    :if
;;    :bind                         ;;    :doc
;;    :bind*                        ;;    :req
;;    :bind-keymap                  ;;    :tag
;;    :bind-keymap*                 ;;    :added
;;    :interpreter                  ;;    :comment
;;    :mode                         ;;    :file
;;    :magic                        ;;    :url
;;    :magic-fallback               ;;    :emacs<
;;    :hook                         ;;    :emacs<=
;;    :commands                     ;;    :emacs=
;;    :init                         ;;    :emacs>
;;    :defer                        ;;    :emacs>=
;;    :demand                       ;;    :package
;;    :load                         ;;    :ensure
;;    :config                       ;;    :feather
;;    :blackout)                    ;;    :straight
                                    ;;    :el-get
                                    ;;    :defaults
                                    ;;    :after
                                    ;;    :commands
                                    ;;    :bind
                                    ;;    :bind*
                                    ;;    :mode
                                    ;;    :interpreter
                                    ;;    :magic
                                    ;;    :magic-fallback
                                    ;;    :hook
                                    ;;    :advice
                                    ;;    :advice-remove
                                    ;;    :pre-setq
                                    ;;    :pl-pre-setq
                                    ;;    :auth-pre-setq
                                    ;;    :custom
                                    ;;    :custom*
                                    ;;    :pl-custom
                                    ;;    :auth-custom
                                    ;;    :custom-face
                                    ;;    :init
                                    ;;    :require
                                    ;;    :global-minor-mode
                                    ;;    :hydra
                                    ;;    :transient
                                    ;;    :combo
                                    ;;    :combo*
                                    ;;    :smartrep
                                    ;;    :smartrep*
                                    ;;    :chord
                                    ;;    :chord*
                                    ;;    :mode-hook
                                    ;;    :leaf-defer
                                    ;;    :setq
                                    ;;    :setq-default
                                    ;;    :pl-setq
                                    ;;    :auth-setq
                                    ;;    :pl-setq-default
                                    ;;    :auth-setq-default
                                    ;;    :delight
                                    ;;    :diminish
                                    ;;    :blackout
                                    ;;    :grugru
                                    ;;    :config
                                    ;;    :defer-config)

https://github.com/conao3/leaf-keywords.el/pull/35

This PR looks like a lot of changed lines, but in effect, with 10 line changes, he could add the keyword :straight. Is this kind of trick possible for use-package?

The leaf was developed by myself only. This is the easiest way to solve the copyright problems that the FSF requires. (see reddit and php-mode wiki)

I think that it is a loss of the world that use-package is not attached to Emacs as a standard. leaf aims to be an Emacs standard attachment, which I hope will become the package configuration standard in Emacs. leaf has this future outlook, so moving from use-package to leaf is a good choice.

conao3 commented 5 years ago

Oops. p is here.

    (defmacro p (form)
      "Output expanded form of given FORM."
      `(progn
         (pp (macroexpand-1 ',form))
         nil))
dylanjm commented 5 years ago

As a relatively new user to Emacs, would you recommend that I start off with use-package first? I'm not familiar with use-package and don't quite know what you're talking about when it comes to the short-comings of use-package. I would rather build my init.el from the ground up the right way, and leaf.el seems to be the way to do it, but I am not really grasping how to use it since it's always referenced to use-package.

conao3 commented 5 years ago

OK. If you are not familiar with Emacs or use-package, you should start by looking at my init.el and get started. This will be good practice for leaf.el.

And if you have any trouble, ask me anything. I have a dedicated Slack ready for that. README has invitation link.

jwiegley commented 4 years ago

Have you seen how use-package implements its own keywords? It does so via an extensible mechanism as well. For example, :magic is implemented using:

(defalias 'use-package-normalize/:magic 'use-package-normalize-mode)
(defalias 'use-package-autoloads/:magic 'use-package-autoloads-mode)

(defun use-package-handler/:magic (name _keyword arg rest state)
  (use-package-handle-mode name 'magic-mode-alist arg rest state))

Any keyword can be added by following this function naming pattern, and then inserting the new keyword into use-package-keywords so that use-package can understand what its priority should be.

conao3 commented 4 years ago

I've seen it, and I'm a use-package contributor. As stated in the README, I've been using use-package for 2.5 years and really still love it, and it's revolutionized the way Emacs configuration. But use-package has a long history, the code has become bloated. Some of the stresses I've felt are as follows.

  1. keyword implementation As far as I'm concerned, your implementation of :magic is problematic. Like :magic, many functions are defined by defalias, and we need to care a lot to change use-package-normalize-mode. Can you easily figure out which keywords depend on use-package-normalize-mode, for example? If even your keywords rely on defalias, it's likely that other user-implemented packages also have aliases for use-package-normalize-mode. In my leaf, I can see at a glance which keywords are sharing code with :mode. https://github.com/conao3/leaf.el/blob/de916f86ea5391c205c03d04fec027bf00bb6d61/leaf.el#L210-L211

  2. Grammar of keywords

    1. Perhaps by a common-lisp analogy, the :disabled keyword is activated by simply writing the keyword. As a result, even if I give nil to :disabled, that use-package will be converted to nil even if I give t. This is a little odd behavior that is different from keywords like :defer, :no-require and :when.

    2. A :after can receive a package symbol or a list of them, but not the :ensure keyword.

    3. :hook and :bind can accept a dot pair or a list of them, but not a :custom keyword.

    4. bind has a broken indentation in some cases of accepted formats.

      
      (use-package term
      :bind (("C-c t" . term)
       :map term-mode-map
       ("M-p" . term-send-up)         ; good indent
       ("M-n" . term-send-down)))

(use-package term :bind (:map term-mode-map ("M-p" . term-send-up) ; indent broken ("M-n" . term-send-down)))


These problems can no longer be fixed, as `use-package` has a million DLs in MELPA and many users' init.el rely on it.

These may be small reasons, but they were enough reasons for me to develop a package called `leaf` from scratch.

Having developed from scratch, my leaf is made up of code only for people who have signed up for FSF.
Your `use-package` is useful, and this should be a standard attachment to Emacs.
There are a lot of hurdles for `leaf` to be attached to Emacs, but `leaf` has at least cleared the licensing issues.

Also, there are already many more features and related packages than `use-package`.
```emacs-lisp
(length use-package-keywords)
;;=> 34

(length (leaf-available-keywords))
;;=> 72

The only use-package related packages are those related to keywords, but leaf aggregates them into leaf-keywords. (MELPA - use-package) There are many unique and interesting packages related to leaf. (MELPA - leaf)

progfolio commented 4 years ago

The point about indentation is moot as of https://github.com/emacs-mirror/emacs/commit/1b2a881c9b3ef9158cbf28a65b9e94c2dbc6cec2

(use-package term
  :bind ( :map term-mode-map
          ("M-p" . term-send-up)
          ("M-n" . term-send-down)))
conao3 commented 4 years ago

weird hack. Why do we add one space indent for all other lines by remove some global binding?

(use-package term
  :bind (("C-c t" . term)
         :map term-mode-map
         ("M-p" . term-send-up)         ; good indent
         ("M-n" . term-send-down)))

;; remove global binding; ("C-c t" . term)
(use-package term
  :bind (:map term-mode-map
              ("M-p" . term-send-up)    ; bad indent
              ("M-n" . term-send-down)))

(use-package term
  :bind ( :map term-mode-map
          ("M-p" . term-send-up)        ; better indent?
          ("M-n" . term-send-down)))

see this diff. We get a diff where it's not relevant. I am not convinced that this is a good choice.

$ diff -u use-package.el.orig use-package.el 
--- use-package.el.orig     2020-10-09 17:17:56.880058593 +0900
+++ use-package.el          2020-10-09 17:18:17.720058370 +0900
@@ -1,5 +1,4 @@
 (use-package term
-  :bind (("C-c t" . term)
-         :map term-mode-map
-         ("M-p" . term-send-up)
-         ("M-n" . term-send-down)))
+  :bind ( :map term-mode-map
+          ("M-p" . term-send-up)
+          ("M-n" . term-send-down)))

see leaf side. This is so simple! Just remove ("C-c t" . term).

(leaf term
  :bind (("C-c t" . term)
         (term-mode-map
          ("M-p" . term-send-up)
          ("M-n" . term-send-down))))

;; remove global binding; ("C-c t" . term)
(leaf term
  :bind ((term-mode-map
          ("M-p" . term-send-up)
          ("M-n" . term-send-down))))
kiennq commented 4 years ago

use-package is actually not needed at runtime https://github.com/jwiegley/use-package#use-packageel-is-no-longer-needed-at-runtime. But from leaf example, it seems leaf is not and the package has to be loaded for leaf-handler-package and leaf-keys to be usable, which increases startup time.

(cort-deftest-with-macroexpand leaf/bind
  '(
    ;; cons-cell will be accepted
    ((leaf macrostep
       :ensure t
       :bind ("C-c e" . macrostep-expand))
     (prog1 'macrostep
       (unless (fboundp 'macrostep-expand) (autoload #'macrostep-expand "macrostep" nil t))
       (declare-function macrostep-expand "macrostep")
       (leaf-handler-package macrostep macrostep nil)
       (leaf-keys (("C-c e" . macrostep-expand)))))))

Can leaf provide no runtime requirement like use-package?

conao3 commented 4 years ago

The test is tested the Sexp expanded by macroexpand-1 so please use macroexpand-all to expand all macros. There're no leaf functions after expanded form.

(ppp-macroexpand-all
 (leaf macrostep
   :ensure t
   :bind ("C-c e" . macrostep-expand)))
;;=> (prog1 'macrostep
;;     (condition-case err
;;         (progn
;;           (if (fboundp 'macrostep-expand)
;;               nil
;;             (autoload #'macrostep-expand "macrostep" nil t))
;;           nil
;;           (if (package-installed-p 'macrostep)
;;               nil
;;             (if (assoc 'macrostep package-archive-contents)
;;                 nil
;;               (package-refresh-contents))
;;             (condition-case _err
;;                 (package-install 'macrostep)
;;               (error
;;                (condition-case err
;;                    (progn
;;                      (package-refresh-contents)
;;                      (package-install 'macrostep))
;;   
;;                  (error
;;                   (display-warning 'leaf
;;                                    (format "In `macrostep' block, failed to :package of macrostep.  Error msg: %s"
;;                                            (error-message-string err))))))))
;;           (let* ((old (lookup-key global-map
;;                                   (kbd "C-c e")))
;;                  (value (list 'global-map "C-c e" 'macrostep-expand
;;                               (and old
;;                                    (not (numberp old))
;;                                    old))))
;;             (setq leaf-key-bindlist (cons value leaf-key-bindlist))
;;             (define-key global-map
;;               (kbd "C-c e")
;;               'macrostep-expand)))
;;   
;;       (error
;;        (display-warning 'leaf
;;                         (format "Error in `macrostep' block.  Error msg: %s"
;;                                 (error-message-string err))))))
semenInRussia commented 2 years ago

In use-package you can use the following construction for correct indentation of the :bind keyword


(use-package tex
  :ensure auctex
  :bind ((:map latex-mode-map)
         ("C-c x" . 'latex-kill-section)))