use-package.elからleaf.elに移行した

この度、use-package.elからleaf.elに移行した。
どうして移行したのかや、その時の調査した周辺技術や、移行に関する知見を、せっかくだしメモしておく。

なお、内容を書くにあたって、Emacs 26.3と[GitHubレポジトリ](https://github.com/yonta/emacs-package-test)のコードで調査している。

# 背景

Emacsのパッケージとその設定を、以下のように管理している。

- dotfiles全般の一部としてGitHubにてGit管理している
- package.elを使っている
    - Emacs24から標準だしね
- use-package.elを使っている
    - メジャーなパッケージ設定を構造的に管理するパッケージ
    - 今年の春くらいに導入したばかり
    - パッケージ設定が構造的に書けて最高の気持ち
- 野良パッケージを、`git add`や`git submodule add`を使って管理している
    - 野良パッケージはpackage.elの管理外のパッケージを指す
    - MELPAに登録されてないものや既存パッケージを一部変更したものがある
        - 例えば、手元のsml-modeにSML#のキーワードを追加している

# 問題点

- GitHubのbranchやforkなどの野良パッケージを、Emacs Lispでキレイに扱いたい
    - use-package.elにこの機能はない
    - この解決のためにgit submoduleを使っているが、
      submoduleの初期化や更新など操作がフクザツなため止めたい
- use-package.elを導入したばかりなので、コストは低く解決したい

# 結論

- use-package.elとその拡張を使うのをやめた
    - 今回の調査では、満足できるuse-package.elの拡張が見つからなかった
- use-package.elからleaf.elへ移行した
    - コストはそこそこ高かったが、完全移行してよかった点が多かった
    - leaf.elはいいぞ
- 一部のgit submoduleは止めれていない
    - 今回は議論しないが、snippetのレポジトリをうまく扱う方法を考えていない

# トライしたメモ

use-package.elはキーワードを拡張できるため、多数の統合拡張がある。
そこで、use-package.elの拡張を初めに調査した。
また、use-package.el以外にも良いパッケージ設定管理ツールがないか調査した。
特に、use-package.elとキーワードが似通っているとなお良い。

ざっくり調査すると以下の3つを見つけ、それぞれ試すことにした。

- use-package.el + quelpa.el
- use-package.el + straight.el
- leaf.el

なお、パッケージ管理のバックエンドとしてはpackage.elは使うことにし、
これ以外のバックエンドの調査はしていない。

## use-package.el + quelpa.el

[Quelpa](https://framagit.org/steckerhalter/quelpa)はパッケージのビルド・インストールを行うツールである。

これ自身にはuse-package.elのようなパッケージ設定管理の機能はない。
代わりに、[quelpa-use-package](https://framagit.org/steckerhalter/quelpa-use-package)というインテグレーションが用意されている。

### 良かった

- use-package.elとのインテグレーションがあるため移行コストが低い
    - use-package節内で`:quelpa`キーワードが使えるようになる
- `:quelpa`キーワードを使って、GitHubのbranchが指定できる
- package.elのパッケージ状態を認識してくれる
    - 依存関係などがいい感じに解決される
    - Quelpaでインストールするとpackage.elのインストールマークをつけてくれる
    - quelpa/buildでビルドしてelpaにコピーしているようだった

### 悪かった

- 存在しないパッケージを`(quelpa PACKAGE ...)`で指定してもエラーが出ない
    - まじか？？？
    - `:quelpa`だとさすがにuse-package.elがエラーを出してくれる
- 開発が終了している

## use-package.el + straight.el

[straight.el](https://github.com/raxod502/straight.el#installing-packages-programmatically)は次世代の純粋関数型のパッケージマネージャらしい。
なお、どの辺が純粋関数型なのかはよくわからなかった。
他のパッケージマネージャとの比較を、しっかりしているのに好感が持てた。

これも自身にはパッケージ設定管理の機能ははない。
代わりに、use-package.elとのインテグレーションを持っている。

### 良かった

- use-package.elとのインテグレーションがあるため移行コストが低い
    - use-package節内で`:straight`キーワードが使えるようになる
- `:straight`キーワードでGitHubのbranchやforkの指定ができる
    - 機能としてもドキュメントとしても便利
- `:straight`キーワードを使うと、デフォルトでEmacsMirrorのパッケージも
  探索してくれて便利だった

### 悪かった

- package.elとの相性が悪い
    - package.elのインストール状態を認識せず、
      straight.elは独自で依存関係も込みでインストールする
    - 結果、依存関係のパッケージが大量に重複してインストールされる

## leaf.el

[leaf.el](https://github.com/conao3/leaf.el)はYet Another use-packageで、パッケージ設定管理のパッケージの1つ。
Emacs Advent Calendarにも多数寄稿している@conao3さんが開発している。
昨年12月にバージョン1.0.0が出た比較的新しいパッケージ。

何度かQiita記事でも目にしており、気になっていた。
YA use-package.elということで、移行コストを低く抑えつつ、
よりよい機能が得られるのではと思って試した。

### 良かった

- package.elのパッケージ状態を認識してくれる
    - Quelpaと同上、というかpackage.elをバックエンドとして使っている？
- GitHubのbranchが指定できる
    - el-get.elとのインテグレーションがあり、`:el-get`を使い実現できる
- leaf節で`:el-get`を使ってインストールすると依存パッケージを解決しない
    - そもそもpackage.elの管理外のものを導入しようとしているため、
      依存関係などを自分で管理するのは妥当と思える
    - leaf節をネストして書けるため、依存関係も書きやすい
    - 導入元がpackage.elとel-get.elとで違うものを、ちゃんと分離して管理できる
- leaf節をネストして書けるため、関連する設定をまとめられる
    - 例えば、companyの設定の中に、company-**の設定を書いておける
    - [use-package.elでもできないことはないが、推奨されていない](https://github.com/jwiegley/use-package/issues/453#issuecomment-347750736)
- 移行しただけでEmacsの起動時間が半分になった
    - use-package.elでは`:defer t`で遅延読み込みの指定がいるが、
      leaf.elではデフォルトで遅延読み込みなのも効いていそう
    - use-package.elは起動時間の統計をとるなどリッチな機能を持っており、
      そもそも遅いのも効いていそう
    - 詳細な原因調査はしていない
    - な に も し て な い の に Emacs が は や く な り ま し た

### 悪かった

- ネストしたleaf節がつくるツリー構造が、パッケージの関係性に合わないものがあった
    - あるモードの設定を、グローバルモードとメジャーモードのどちらに
      まとめるか問題
    - 例えば、company-jediはpython-modeでcompanyの補完を提供するが、
      機能としてcompanyにまとめるべきか、modeとしてpythonにまとめるべきか
    - 好きにすればいいと思った、書ける自由度があるのは良い
- use-package.elと書き方違いが多少あり、まあまあ移行コストがかかった
    - 1,000行ほどの元のelファイルに対して、500行ほどのdiffがあった

### leaf.el移行での変更点や感じたこと

- キーワード名が一部違うことがある
    - `:defer t`に対応するのは`:leaf-defer t`など
- `:custom`の引数のデータ構造と許される値が違う
    - `:custom`の引数がリストではなくドットペア
    - `:custom (x . (1+ a))`のような未評価部は許されていない
        - `defvar b (1+ a)`とかして`:custom (x . b)`とする
- デフォルトでパッケージがrequireされない
    - requireが必要なパッケージには`:require t`をつける
- 遅延読み込みがデフォルトとなっている
- `:bind`でのmap名の書き方が違う
    - `(:map xxx-mode-map ...)`を`(:xxx-mode-map ...)`にする