3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

use-packageのマクロ展開を調査

Posted at

本記事では use-package の 2.4.4 を対象に、マクロ展開後のコードを例示します。
対象とする読者は他の人の設定を参考に use-package を使っているが、内部処理の詳細を理解できていない方です。

本記事と合わせて @conao3 さんの https://qiita.com/conao3/items/dc88bdadb0523ef95878 を読んでいただくとより理解が深まります。
上記の記事にはleaf.elだけでなくuse-packageの解説が含まれます。
本記事で行うようなマクロ展開の例も含まれており、実質的にこの記事の上位互換の記事です。

なお筆者はSpacemacsを使用しているため、環境依存による差異があるかもしれません。

use-package について

use-packageはパッケージの読み込みや設定を統一的なフォーマットで行えるようにするためのマクロです。
:ensure キーワードを使ってパッケージの読み込みをする機能はあるものの、本質的な用途はインストール済みのパッケージの設定ということになります。

当初、筆者は「マクロである」と言われても、あまりピンときませんでしたが、実際に展開されたコードを見ると本質的な機能はシンプルであることが分かりました。
デフォルトの設定のままマクロ展開を行ったコードにはエラー処理が含まれるため、シンプルであることに気づきづらいことも事実です。

本記事では事前設定としてuse-packageの設定をいくつか紹介し、その後pp, macroexpandの関数を使ったマクロの展開の例を紹介します。

事前設定

マクロ展開後のコードを簡略化するため下記の設定を行います。

use-package-expand-minimally

(setq use-package-expand-minimally t)

エラー処理などを省いた、簡略化したコードで展開をするオプションです。
docsには

It can also help with reading macro-expanded
definitions, to understand the main intent of what's happening.
(翻訳) マクロ展開された定義を読むことで、主な処理の概要を理解することに役立ちます

とある通り、全体像の理解に役立つためこのオプションを有効にします。

use-package-inject-hooks

(setq use-package-inject-hooks nil)

default値はnilです。
spacemacs ではこのオプションが有効化されているため、nilに戻します
https://github.com/syl20bnr/spacemacs/blob/b28d65b7aa7a936cdab7fc0453df86b21ac1496f/core/core-use-package-ext.el#L75

use-package-use-theme

(setq use-package-use-theme nil)

:custom キーワードの展開後のコードを簡略化します。
このオプションに nil 以外の値を設定すると :custom で設定した値が custom file に保存されることを防ぐことができるオプションのようです。

マクロ展開

README.md の「Getting started」セクションの設定例をマクロ展開してみます。

最もシンプルな例

キーワードを何も与えなければ、展開されるコードは require 関数1行のみです。

展開前.el
(use-package foo)
展開.el
(setq use-package-expand-minimally t)
(setq use-package-inject-hooks nil)

(pp
 (macroexpand
  '(use-package foo)))

;; (require 'foo nil nil)

:init

:init キーワードに記載した処理は require 関数の前に実行されます。

展開前.el
(use-package foo
  :init
  (setq foo-variable t))
展開.el
(pp
  (macroexpand
    '(use-package foo
      :init
      (setq foo-variable t))))
;; (progn
;;   (setq foo-variable t)
;;   (require 'foo nil nil))

:init:config の併用

:init の処理は require の前に :config の処理はrequire関数の後に実行されます。

展開前.el
(use-package foo
  :init
  (setq foo-variable t)
  :config
  (foo-mode 1))
展開.el
(pp
 (macroexpand
  '(use-package foo
     :init
     (setq foo-variable t)
     :config
     (foo-mode 1))))
;; (progn
;;   (setq foo-variable t)
;;   (require 'foo nil nil)
;;   (foo-mode 1)
;;   t)

use-packageのREADME.mdではより複雑な例が記載されていますが、同様の方法でマクロ展開後のコードを調べることができます。

:init, :config, :customの併用

最後に :init, :config, :custom の3つのキーワードのマクロ展開例を記載します。

展開後のコードを見ると :custom, :init はrequire前に、:configはrequire後に出力されることが分かります。

展開前.el
(use-package foo
     :init
     (setq foo-variable t)
     :config
     (foo-mode 1)
     :custom
     (bar-variables t)
     )
展開.el
(setq use-package-use-theme nil)
(pp
 (macroexpand
  '(use-package foo
     :init
     (setq foo-variable t)
     :config
     (foo-mode 1)
     :custom
     (bar-variables t)
     )))
;; (progn
;;   (customize-set-variable 'bar-variables t "Customized with use-package foo")
;;   (setq foo-variable t)
;;   (require 'foo nil nil)
;;   (foo-mode 1)
;;   t)

おわりに

筆者がuse-packageの設定中に陥った「:custom キーワードの処理はrequireに呼び出される」という誤解が、記事執筆のきっかけとなりました。
実際には:customキーワードの処理はrequireに呼び出されます
(より正確には use-package-keywords の設定に準ずるが正しいはずです)

ppmacroexpand を使いながら展開後のコードを読み解く方法は、他のライブラリでも参考にできるため、自分向けの備忘録として公開しておこうと思います。

参考文献

  • [正式リリース]leaf.elで雑然としたEmacs設定ファイル「init.el」をクリーンにする
3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?