2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

go fixのインライン展開でアニメーションを作る

2
Last updated at Posted at 2026-07-11

TL; DR

  • 関数に //go:fix inline ディレクティブを付けると、呼び出し箇所が go fix ./... でインライン展開される
  • 関数定義を循環させると、呼び出し箇所のインライン展開の置換がループする
  • ループを利用してアニメーションが可能

はじめに

みなさん、プログラミングしてますか?AIに丸投げしてますか?

AIでも人間でも、書いたコードの仕上げにはフォーマッタやコード修正ツールの利用が欠かせません。
なんといっても、ソースコードがいっせいに動き出し整列するさまは見ていて壮観です。

この躍動感、もっと味わいたくないですか?

というわけで本記事では、go fixによるソースコード修正で アニメーションを作る 方法を紹介します。

ezgif-75b7933823284108.gif

go fixコマンドによるインライン展開

go fix コマンドを使うと、自動でコードのリファクタリングができます。go fmt のようなシンプルな整形ではなく、例えばfor文を最新版の書き方に置き換える1といった構文レベルでの修正を行います。

さらに、Go1.26で go fix コマンドにインライン展開の機能が追加されました。
以下のように //go:fix inline ディレクティブを付けると、関数呼び出しをインライン展開することができます。

// ReadFile reads the file named by filename…
// Deprecated: As of Go 1.16, this function simply calls [os.ReadFile].
//go:fix inline
func ReadFile(filename string) ([]byte, error) {
    return os.ReadFile(filename)
}

以下の呼び出し箇所が

before
data, err := ReadFile("hello.txt")

このようにインライン展開されます。

after
// ReadFile関数の中身に置換される
data, err := os.ReadFile("hello.txt")

主なユースケースは古い関数の自動置き換えです。
上記のようにライブラリ提供者がdeprecated関数に //go:fix inline をつけておくと、利用者は go fix するだけで新しい関数に自動で置き換えることができます。

インライン展開をループさせてアニメーションを作る

インライン化は繰り返し適用可能です。入れ子上に関数定義されている場合、go fix するたびに、マトリョーシカのように1つずつインライン展開されます。

//go:fix inline
func A() {
	B()
}

//go:fix inline
func B() {
	fmt.Println("b")
}
A()
B()
fmt.Println("b")

さらに、関数を循環定義させればインライン展開が無限ループします。これがアニメーションの仕組みです。

//go:fix inline
func A0() {
	A1()
}

//go:fix inline
func A1() {
	A2()
}

//go:fix inline
func A2() {
	A0()
}

ここで1点注意です。関数呼び出しの置換は

A0()A1()A2()A0()...

ではなく

A0()A1()A0()...

となります。

というのも、go fix によって 関数定義内の関数呼び出しもインライン展開されるためです。

go fix実行前
//go:fix inline
func A0() {
	A1()
}

//go:fix inline
func A1() {
	A2()
}

//go:fix inline
func A2() {
	A0()
}
go fix1回実行
//go:fix inline
func A0() {
	A2()
}

//go:fix inline
func A1() {
	A0()
}

//go:fix inline
func A2() {
	A1()
}
go fix2回実行
//go:fix inline
func A0() {
	A1()
}

//go:fix inline
func A1() {
	A2()
}

//go:fix inline
func A2() {
	A0()
}

具体的には、gofix適用ごとに関数呼び出しの添え字の増え方が1, 2, 4...と倍々に増えていきます(A0A1A3A7)。
上記の例だと関数は3つしかないため、実際には3で割ったあまりの A0A1A0A1 となります。

同様の理由で、関数の個数を 2^n にするとどこかのタイミングで添え字の増加を 2^n で割ったあまりが0になってしまいアニメーションが止まってしまいます。

あとは、関数定義をアニメーションに使いたい行数分用意し、関数名をアスキーアートにすれば冒頭のアニメーションになります。

ezgif-75b7933823284108.gif

アニメーション生成CLI

とはいえ毎回手で書くのは面倒なので、各フレームのアスキーアートからアニメーションソースコードを自動生成するツールを作りました2

以下のようなアスキーアートファイルからソースコードを生成します。

1フレーム目
_OOO_
O____
O_OO_
O___O
_OOO_
2フレーム目
_OOO_
O___O
O___O
O___O
_OOO_

(以下同様に10フレーム目まで作成)

完成したソースコードがこちらです。

生成結果ソースコード(クリックで展開)
// Source code generated by gofixanime.
// Apply go fix ./... to animate the ASCII art in main.
package main

//go:fix inline
func _OOO_() {
        _OOO__()
}

//go:fix inline
func O_____() {
        O___O_()
}

//go:fix inline
func O_OO___() {
        O___O__()
}

//go:fix inline
func O___O___() {
        O___O____()
}

//go:fix inline
func _OOO_____() {
        _OOO______()
}

//go:fix inline
func _OOO__() {
        O___O()
}

//go:fix inline
func O___O_() {
        OO_OO_()
}

//go:fix inline
func O___O__() {
        O_O_O__()
}

//go:fix inline
func O___O____() {
        O___O_____()
}

//go:fix inline
func _OOO______() {
        O___O______()
}

//go:fix inline
func O___O() {
        OOOOO()
}

//go:fix inline
func OO_OO_() {
        O______()
}

//go:fix inline
func O_O_O__() {
        OOOO___()
}

//go:fix inline
func O___O_____() {
        O_______()
}

//go:fix inline
func O___O______() {
        O________()
}

//go:fix inline
func OOOOO() {
        O___O_______()
}

//go:fix inline
func O______() {
        _O_O__()
}

//go:fix inline
func OOOO___() {
        __O____()
}

//go:fix inline
func O_______() {
        _O_O____()
}

//go:fix inline
func O________() {
        O___O________()
}

//go:fix inline
func O___O_______() {
        OOOOO_()
}

//go:fix inline
func _O_O__() {
        O_________()
}

//go:fix inline
func __O____() {
        OOOO____()
}

//go:fix inline
func _O_O____() {
        O__________()
}

//go:fix inline
func O___O________() {
        OOOOO____()
}

//go:fix inline
func OOOOO_() {
        OOOOO__()
}

//go:fix inline
func O_________() {
        __O___()
}

//go:fix inline
func OOOO____() {
        __O_____()
}

//go:fix inline
func O__________() {
        __O______()
}

//go:fix inline
func OOOOO____() {
        OOOOO_____()
}

//go:fix inline
func OOOOO__() {
        OOOOO___()
}

//go:fix inline
func __O___() {
        __O_______()
}

//go:fix inline
func __O_____() {
        __O________()
}

//go:fix inline
func __O______() {
        __O_________()
}

//go:fix inline
func OOOOO_____() {
        OOOOO______()
}

//go:fix inline
func OOOOO___() {
        O___O_________()
}

//go:fix inline
func __O_______() {
        OO__O_()
}

//go:fix inline
func __O________() {
        O_O_O___()
}

//go:fix inline
func __O_________() {
        O__OO___()
}

//go:fix inline
func OOOOO______() {
        O___O__________()
}

//go:fix inline
func O___O_________() {
        _OOO___()
}

//go:fix inline
func OO__O_() {
        O___O___________()
}

//go:fix inline
func O_O_O___() {
        OOOOO_______()
}

//go:fix inline
func O__OO___() {
        O___O____________()
}

//go:fix inline
func O___O__________() {
        O___O_____________()
}

//go:fix inline
func _OOO___() {
        _____()
}

//go:fix inline
func O___O___________() {
        ______()
}

//go:fix inline
func OOOOO_______() {
        _______()
}

//go:fix inline
func O___O____________() {
        ________()
}

//go:fix inline
func O___O_____________() {
        _________()
}

//go:fix inline
func _____() {
        _OOO_()
}

//go:fix inline
func ______() {
        O_____()
}

//go:fix inline
func _______() {
        O_OO___()
}

//go:fix inline
func ________() {
        O___O___()
}

//go:fix inline
func _________() {
        _OOO_____()
}
func main() {
        _OOO_()
        O_____()
        O_OO___()
        O___O___()
        _OOO_____()
}

CLIの実装

史上最高の頭脳Fableにブン投げることにより13分で実装完了しました。 Fableの無駄遣い

まるなげぷろんぷと(クリックで展開)
`//go:fix inline` を利用してソースコード上にアニメーションを描画する CLI `gofixanime` を実装してください。
Fableのクレジットが足りなくなりそうであれば、適宜他のモデルのサブエージェントを使用してください。

# `//go:fix inline` のアニメーションの原理
`//go:fix inline` を使用すると関数がインライン展開されます。そこで、関数を循環定義すると、`go fix ./...` を適用するたびに関数呼び出しの中身がインライン展開し、展開による置換がループします。このループを使ってアニメーションを作ります。

```go
package a

//go:fix inline
func A0() {
	A1()
}

//go:fix inline
func A1() {
	A2()
}

//go:fix inline
func A2() {
	A3()
}

//go:fix inline
func A3() {
	A4()
}

//go:fix inline
func A4() {
	A0()
}

func main() {
	// go fix ./... すると A0 -> A1 -> A3 -> A2 -> A0 とループ
	A0()
}
```

関数定義がn個ある場合、`go fix` をi回適用すると 0番目の関数の関数呼び出し(上記の例だと`A0()`) は k番目(ただし `k \equiv 2^i (mod n)` の関数呼び出しに置換されます。
上記からもわかるように、アニメーションを作りたい場合iフレーム目の表示はi番目の関数定義とはならない点に注意が必要です。
また、nによっては kがある値から動かなくなってしまう場合があり(例: nが2の累乗)、そのようなnを選ばないようにする必要があります。

アスキーアートを行ごとに分解し上記のような循環定義の関数にすることで、より大規模なアニメーションを作成可能です。
今回作成するgofixanimeは、与えられた複数のアスキーアート文字列ファイルからgo fixでアニメーションができるmain.goファイルを生成するCLIです。

```go
main() {
	// gのアスキーアート(以降 o, f, i, x のアスキーアートに置換されることでアニメーション)
	// NOTE: 各行で名前が衝突しないよう、末尾に行数に応じた_を付け名前が一意になるようにする
	__OOO_()
	_O___O_()
	_O___O__()
	_OOOOO___()
	_____O____()
	__OOO______()
}
```

## 仕様

- `gofixanime ${アスキーアートテキストファイルを格納したディレクトリ名}` でソースコード生成
- アスキーアートテキストファイルは、ディレクトリ配下の連番のテキストファイル `0.txt`, `1.txt`...
- アスキーアートに関数定義で使用できない文字列が含まれる場合はエラー
- アスキーアートテキストファイルは全ファイル行数、列数が同じである必要がある(違った場合エラー)
- アスキーアートテキストファイルの個数がgo fixのアニメーションが成立しない個数(go fixを適用し続けるとアニメーションが止まってしまう)である場合はエラー
- ソースコード生成には "github.com/dave/jennifer/jen" を使用
- ソースコードはTDDで作成

ソースコードの生成にはJenniferを使用しています。ビルダーパターンを使用して構文の要素を組み合わせていきます。

internal/anime/gen.go
	for pos := range frames {
		for r := range names[pos] {
			// cyclic definition: the row function at pos calls the same row at the next position
			f.Comment("//go:fix inline")
			f.Func().Id(names[pos][r]).Params().Block(
				jen.Id(names[(pos+1)%n][r]).Call(),
			)
		}
	}

また、フレームを関数定義化する際に別行の定義と混ざってしまわないよう、行番号に応じた長さのsuffixを付けて区別しています。

生成されたソースコード
func main() {
	// 関数名の末尾に_を付けて、各行の関数定義が一意になるようにしている
	_OOO_()
	O_____()
	O_OO___()
	O___O___()
	_OOO_____()
}

おわりに

以上、go fixのインライン展開でアニメーションを作る紹介でした。
go fixにはまだまだ可能性を感じているので、皆さまもぜひ置換で遊んでみてはいかがでしょうか?

  1. Go1.21まではfor文のループ中で1つのループ変数を使いまわしていました。そのため、クロージャで変数をキャプチャし後で実行したり、ゴルーチン等で非同期処理したりすると、イテレーションが進んでおり想定と異なる値になってしまいます。これを回避するため、同名のローカル変数に再代入するという実装が主流でした(for _, x := range s { x := x ... })。Go1.22から、ループ変数はイテレーションごとに作成されるようになりましたgo fix では不要になったこの代入 x := x が削除されます。

  2. 前述の通りループ内には表示されないフレームもある(A0, A1, A2の3フレームだとA2は表示されない)ため、余分なダミーフレームを用意してあげる必要があります

2
1
2

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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?