関数を返す関数を呼び出せる関数を返す関数を書く

どうも意味分からんタイトルですみません。でもこのタイトルのことをやるのがこの記事の目的です。

# packer.nvim でよく書くこれ

[packer.nvim][] でプラグインを追加する際、`setup` 関数を設定の度に呼び出すことがよくあります。

[packer.nvim]: https://github.com/wbthomason/packer.nvim

```lua
use {
  "rcarriga/nvim-notify",
  config = function()
    require("notify").setup {}
  end,
}
```

こういうのしょっちゅうあると思うんですが、なんかいちいち `function() …… end` で囲むのダルくないですか？ `function` って文字数多いしイヤなんですよね。

```lua
use {
  "rcarriga/nvim-notify",
  config = lazy_require("notify").setup {},
}
```

こう書けたらスマートですよね。これができるようになるプラグインを作りました。

* [delphinus/lazy_require.nvim: A tiny module to define function to load another module lazily.][lazy_require.nvim]

[lazy_require.nvim]: https://github.com/delphinus/lazy_require.nvim

# ふーん……で、何がすごいの？

いや意外と難しいんですよ。これ。理由は [packer.nvim][] の「コンパイル」機能にあります。例えば上記の [rcarriga/nvim-notify][] のための設定は `PackerCompile` コマンドによって以下のようなコードに「コンパイル」されます。

[rcarriga/nvim-notify]: https://github.com/rcarriga/nvim-notify

```lua
["nvim-notify"] = {
  config = { "\27LJ\2\n8\0\0\3\0\3\0\a6\0\0\0'\2\1\0B\0\2\0029\0\2\0004\2\0\0B\0\2\1K\0\1\0\nsetup\vnotify\frequire\0" },
  loaded = false,
  needs_bufread = false,
  only_cond = false,
  path = "/Users/jinnouchi.yasushi/.local/share/nvim/site/pack/packer/opt/nvim-notify",
  url = "https://github.com/rcarriga/nvim-notify"
},
```

`config` 関数が訳分かんない文字列になってますね。これバイトコードなんです。packery.nvim は高速化のため、ユーザーの設定を中間表現にコンパイルして保持し、Neovim 起動時には高速に読み込んでくれるんですね。

Lua でコードを中間表現に変換し、それをまた実行するには次のようにします。

```lua
local function print_hoge()
  print "hogehogeo"
end

-- 中間表現に変換
local byte_code = string.dump(print_hoge)

-- もう一度関数に変換
local hoge_fn = assert(loadstring(byte_code))
hoge_fn()
```

これをファイルに保存して実行すると `hogehogeo` と表示されます。ここでもう一度上述の設定行を見てみます。

```lua
  config = function()
    require("notify").setup {}
  end,
```

packer.nvim は `config` に関数が指定されるとこれを `string.dump` でバイト列に変換し、Neovim 実行時は逆に `loadstring` で関数に変換しているのですね。

# 任意のモジュールを `require` して結果を返す関数を返す関数

まずはこれを書かないといけません。どういうことかと言うと、[packer.nvim][] でプラグインを追加している部分では生の `require "notify"` を書いてしまうと `notify` モジュール自体を表してしまいます。欲しいのは実行時に `notify` モジュールを返してくれる関数であって、`notify` モジュール自体ではありません。

```lua
function lazy_require(module)
  return function()
    require(module)
  end
end

lazy_require "notify"  --> notify モジュールを返す関数
lazy_require "notify" ()  --> notify モジュール
```

となっていい感じです。

# 任意のモジュールの関数を呼び出せるようにする

しかし、最終的にやりたいのはモジュール自体を手に入れることではなく、モジュールの `setup` 関数を呼び出したい、ということでした。

```lua
function lazy_require(module)
  return function()
    return {
      setup = function(opts)
        return function()
          require(module).setup(opts)
        end
      end,
    }
  end
end

lazy_require("notify").setup {}  --> require("notify").setup {} を行う関数
```

`setup` 以外の任意の関数を呼び出せるようにするなら、ちょっと複雑ですがメタテーブルを使えば良いです。

```lua
function lazy_require(module)
  return function()
    return setmetatable({}, {
      __index = function(_, method)
        return function(opts)
          return function()
            require(module)[method](opts)
          end
        end
      end,
    })
  end
end

lazy_require("notify").setup {}  --> require("notify").setup {} を行う関数
lazy_require("notify").history(1)  --> require("notify").history(1) を行う関数
```

できましたね。これで完成！……ではないんですね。

# `loadstring` するとクロージャは使えない

そうなんです。`loadstring` で作成された関数はあくまでコード自体を中間表現にしたものを表しているだけで、呼び出された時のコンテキストは保持していません。上記の実装の場合、

```lua
config = lazy_require("notify").setup {}
```

これが [packer.nvim][] によって `string.dump` → `loadstring` されると、以下のような意味になってしまいます。

```lua
-- 以下は疑似コードです。
config = function()
  require(module)[method](opts)
end
```

`module`, `method`, `opts` のような、関数外で定義された変数は参照できないのですね。つまり、クロージャが扱えないんです。

これをどう解決するのか悩んだんですが、クロージャをただの関数にしてしまうことにします。どうするかと言えば、変数の値を含め、バイナリ（中間表現）にして保持してしまうのです。

# クロージャを中間表現で表す

先ずは、`lazy_require "notify"` の部分を考えます。

```lua
function lazy_require(module)
  return require(module)
end
```

こういうやつですね。これを中間表現にする関数は以下のように書けます。

```lua
local function byte_code_require(module)
  local f = assert(loadstring(('return require "%s"'):format(module)))
  return string.dump(f)
end
```

`byte_code_require "notify"` などとすると、`require "notify"` の結果を返す関数、を中間表現にして返してくれます。実際に見てみると次のようなバイナリです。

```lua
local byte_code = byte_code_require "notify"
print(vim.inspect(byte_code))
--> "\27LJ\2\b\23return require \"notify\")\2\0\3\0\2\0\3\4\0\0016\0\0\0'\2\1\0D\0\2\0\vnotify\frequire\1\1\1\0\0"
--> と表示されます。
```

バイナリの中に `return require \"notify\"` のような文字列があり、引数として指定したモジュール名が埋め込まれていることが分かりますね。これなら `loadstring` してもコンテキストが再現可能です。

次に、`lazy_require("notify").setup {}` のように、任意のプロパティの関数を呼び出す関数の中間表現を返してみましょう。

```lua
local function index(self, method)
  return function(opts)
    local f_str = ([[
      local module = assert(loadstring(%s))()
      local opts = assert(loadstring(%s))()
      module.%s(opts)
    ]]):format(vim.inspect(self.load_module), vim.inspect(serialize(opts)), method)
    return assert(loadstring(f_str))
  end
end

return function(module)
  return setmetatable({
    load_module = byte_code_require(module),
  }, {
    __index = index,
  })
end
```

ううむ。一気に難しくなりましたね。まず、ここではメタテーブルの `__index` に関数をセットし、プロパティを参照した時の動作を定義しています。関数 `index()` には `self`、つまりそのテーブル自身と、`method`、参照しようとした関数名が渡されます。例えば、`lazy_require("notify").setup {}` という記述は、


| 変数 | 内容 |
|--|--|
| `self` | `{ load_module = byte_code_require "notify" }` |
| `method` | `"setup"` |
| `opts` | `{}` |


のようになる訳です。関数の中では Lua のコードを文字列で作り、それを中間表現で返しています。

ここで、中間表現のバイナリを実行し、結果を得る方法をおさらいしておきましょう。あるバイナリが変数 `foo` に入っている時、そのコードを実行して結果を得るには次のような構文を使います。

```lua
local result = assert(loadstring(foo))()
```

上述の `index()` では次のようにコードを構成していますが、

```lua
local module = assert(loadstring(%s))()
local opts = assert(loadstring(%s))()
```

`%s` にはそれぞれ `byte_code_require "notify"` の返り値と、`opts` をシリアライズした文字列が入ります。これによりモジュール本体と、最終的に渡すオプション `opts` が手に入ります。そして最後に、

```lua
module.%s(opts)
```

のようにして呼び出しているのですね。`%s` には関数の名前、つまり今回の場合は `"setup"` が入ります。これで求めるものが手に入りました。

:::note info

「シリアライズ」ってサラッと書きましたが、Lua ではこれも中々熱いテーマです。何しろ言語として提供されている機能ではないので様々な実装が存在します。今回は [Tony Finch さんの実装][tony_finch]を使いました。同種の実装・ライブラリは沢山ありますので以下のページを参照してください。

[tony_finch]: http://lua-users.org/lists/lua-l/2009-11/msg00533.html

* [lua-users wiki: Table Serialization](http://lua-users.org/wiki/TableSerialization)

:::

# こんなに苦労したけど……

個人的には結構複雑なことやった気がするんですが、結果として得られたのは、

```lua
config = function()
  require("notify").setup {}
end
```

これを、

```lua
config = lazy_require("notify").setup {}
```

こう書けるようになっただけです。それでも苦労したことを書いとかないと多分自分でも忘れちゃうのでここに纏めておきます。

:::note info

こんなに苦労する原因は、[packer.nvim][] が `config` オプションに与えられた関数を「コンパイル」してしまうからです。このような分かりにくさを嫌ってか、packer.nvim の次期バージョンは「コンパイル機能」を全く使わないものになる予定です。現在リポジトリの `main` ブランチで開発されているので参照してください。

:::

# こっちも見てね！

今回は Advent Calendar にもう 1 個参加してます。この記事はネタみたいなものですが、↓はかなり本気で書いたので是非読んでください。

* [爆速で起動する Neovim を作る - Qiita](https://qiita.com/delphinus/items/fb905e452b2de72f1a0f)