🎄

実例 再帰型定義とRGBA / TypeScript一人カレンダー

2022/12/24に公開約4,800字

こんにちは、クレスウェア株式会社の奥野賢太郎 (@okunokentaro) です。本記事はTypeScript 一人 Advent Calendar 2022の24日目です。昨日は『実例 extends Error』を紹介しました。

過去の頑張りを話します

筆者の参加している案件のひとつに、CSS in JSを採用している案件がありました。具体的にはEmotionを使っていました。そこで色を示すRGB値やAlpha値を取り扱うべくRGBA型という型が生まれました。

次のサンプルコードは、かつて使われた実際のコードです。

type UInt8 =
  | 0
  | 1
  | 2
  | 3
  | 4
  | 5
  | 6
  | 7
  | 8
  | 9
  | 10
  | 11
  | 12
  | 13
  | 14
  | 15
  | 16
  | 17
  | 18
  | 19
  | 20
  | 21
  | 22
  | 23
  | 24
  | 25
  | 26
  | 27
  | 28
  | 29
  | 30
  | 31
  | 32
  | 33
  | 34
  | 35
  | 36
  | 37
  | 38
  | 39
  | 40
  | 41
  | 42
  | 43
  | 44
  | 45
  | 46
  | 47
  | 48
  | 49
  | 50
  | 51
  | 52
  | 53
  | 54
  | 55
  | 56
  | 57
  | 58
  | 59
  | 60
  | 61
  | 62
  | 63
  | 64
  | 65
  | 66
  | 67
  | 68
  | 69
  | 70
  | 71
  | 72
  | 73
  | 74
  | 75
  | 76
  | 77
  | 78
  | 79
  | 80
  | 81
  | 82
  | 83
  | 84
  | 85
  | 86
  | 87
  | 88
  | 89
  | 90
  | 91
  | 92
  | 93
  | 94
  | 95
  | 96
  | 97
  | 98
  | 99
  | 100
  | 101
  | 102
  | 103
  | 104
  | 105
  | 106
  | 107
  | 108
  | 109
  | 110
  | 111
  | 112
  | 113
  | 114
  | 115
  | 116
  | 117
  | 118
  | 119
  | 120
  | 121
  | 122
  | 123
  | 124
  | 125
  | 126
  | 127
  | 128
  | 129
  | 130
  | 131
  | 132
  | 133
  | 134
  | 135
  | 136
  | 137
  | 138
  | 139
  | 140
  | 141
  | 142
  | 143
  | 144
  | 145
  | 146
  | 147
  | 148
  | 149
  | 150
  | 151
  | 152
  | 153
  | 154
  | 155
  | 156
  | 157
  | 158
  | 159
  | 160
  | 161
  | 162
  | 163
  | 164
  | 165
  | 166
  | 167
  | 168
  | 169
  | 170
  | 171
  | 172
  | 173
  | 174
  | 175
  | 176
  | 177
  | 178
  | 179
  | 180
  | 181
  | 182
  | 183
  | 184
  | 185
  | 186
  | 187
  | 188
  | 189
  | 190
  | 191
  | 192
  | 193
  | 194
  | 195
  | 196
  | 197
  | 198
  | 199
  | 200
  | 201
  | 202
  | 203
  | 204
  | 205
  | 206
  | 207
  | 208
  | 209
  | 210
  | 211
  | 212
  | 213
  | 214
  | 215
  | 216
  | 217
  | 218
  | 219
  | 220
  | 221
  | 222
  | 223
  | 224
  | 225
  | 226
  | 227
  | 228
  | 229
  | 230
  | 231
  | 232
  | 233
  | 234
  | 235
  | 236
  | 237
  | 238
  | 239
  | 240
  | 241
  | 242
  | 243
  | 244
  | 245
  | 246
  | 247
  | 248
  | 249
  | 250
  | 251
  | 252
  | 253
  | 254
  | 255;

type ToFirstDecimalPlace =
  | 0
  | 0.1
  | 0.2
  | 0.3
  | 0.4
  | 0.5
  | 0.6
  | 0.7
  | 0.8
  | 0.9
  | 1;

export type RGBA = {
  readonly r: UInt8;
  readonly g: UInt8;
  readonly b: UInt8;
  readonly a: ToFirstDecimalPlace;
};

ああ…長い!256個のLiteral Typesをすべて列挙してUnion Typesにしてしまう、涙ぐましいですね。

これは、当時256個の型によるUnionを、何らかの実装を用いて再帰的に定義しようとするとエラーとなったからでした。とはいえ、この程度の用途であればr: numbera: numberと定義して範囲外の数値を格納しないようにすれば十分とも言えるためやや過剰であり、どちらかというとジョークの側面があります。

当時なぜ0 | 1 | 2 ... 254 | 255を短い行数の型定義で再帰的に生成できなかったのか、それはTypeScriptの再帰の上限が関係します。

TypeScriptの限界

TypeScriptにはいくつかの限界があります。Tupleの長さや再帰の回数がその対象です。

次に紹介する記事は日本人によってTypeScriptの型システムプログラミングの限界に挑んでいる方々によって書かれたものです。これらの記事のように、理論上はチューリング完全だとしても実際はなんでもできるわけではないという事情が汲み取れます。

2021年8月、TypeScriptの生みの親アンダース・ヘルスバーグ氏によって提出されたPull requestは、そういった制限を緩和する内容としてとても興味深いです。

https://github.com/microsoft/TypeScript/pull/45711

この改良によって末尾再帰最適化が適用され、飛躍的に処理上限が引き上げられました。

RGBA

筆者自身では、まだ残念ながらこのコンパイラの性能向上に伴ったRGBA型のリファクタリングを実施できていないのですが、やはり世界にはこのような課題に取り組んでいる開発者が他にいました。

https://catchts.com/range-numbers

この記事で紹介されているComputeRange<N, Result>型によって0から255までのUnion Typesの生成に成功しており、256行も繰り返し書き続けるという愚直な行いをする必要はもうなくなりました。

TypeScript 5.0

TypeScriptもとうとう2023年には5.0を迎えます。

https://github.com/microsoft/TypeScript/issues/51362

すでにチューリング完全となっており、さらにコンパイラ上の制限も緩和する改良が行われている今、さらにどういった機能が追加されるのか筆者としては楽しみでならないです。

明日は『実例 assertMatchedType()

いよいよ明日は最終日です!

明日はTypeScript 一人 Advent Calendar 2022の1日目から24日目までに紹介してきたTypeScriptの、ありとあらゆる仕様をフル活用して注ぎ込んだ集大成assertMatchedType()の実装を紹介します。

最後までお付き合いをよろしくお願いいたします。それではまた。

Discussion

ログインするとコメントできます