実例 再帰型定義とRGBA / TypeScript一人カレンダー
こんにちは、クレスウェア株式会社の奥野賢太郎 (@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: number
やa: number
と定義して範囲外の数値を格納しないようにすれば十分とも言えるためやや過剰であり、どちらかというとジョークの側面があります。
当時なぜ0 | 1 | 2 ... 254 | 255
を短い行数の型定義で再帰的に生成できなかったのか、それはTypeScriptの再帰の上限が関係します。
TypeScriptの限界
TypeScriptにはいくつかの限界があります。Tupleの長さや再帰の回数がその対象です。
次に紹介する記事は日本人によってTypeScriptの型システムプログラミングの限界に挑んでいる方々によって書かれたものです。これらの記事のように、理論上はチューリング完全だとしても実際はなんでもできるわけではないという事情が汲み取れます。
- (ネタ) TypeScript 型パズルで作るmini interpreter
- TypeScriptの型レベルプログラミングで足し算・引き算・掛け算を実装する
- TypeScriptの型で遊ぶ時、再帰制限を無効化する
2021年8月、TypeScriptの生みの親アンダース・ヘルスバーグ氏によって提出されたPull requestは、そういった制限を緩和する内容としてとても興味深いです。
この改良によって末尾再帰最適化が適用され、飛躍的に処理上限が引き上げられました。
RGBA
改
筆者自身では、まだ残念ながらこのコンパイラの性能向上に伴ったRGBA
型のリファクタリングを実施できていないのですが、やはり世界にはこのような課題に取り組んでいる開発者が他にいました。
この記事で紹介されているComputeRange<N, Result>
型によって0
から255
までのUnion Typesの生成に成功しており、256行も繰り返し書き続けるという愚直な行いをする必要はもうなくなりました。
TypeScript 5.0
TypeScriptもとうとう2023年には5.0を迎えます。
すでにチューリング完全となっており、さらにコンパイラ上の制限も緩和する改良が行われている今、さらにどういった機能が追加されるのか筆者としては楽しみでならないです。
assertMatchedType()
』
明日は『実例 いよいよ明日は最終日です!
明日はTypeScript 一人 Advent Calendar 2022の1日目から24日目までに紹介してきたTypeScriptの、ありとあらゆる仕様をフル活用して注ぎ込んだ集大成assertMatchedType()
の実装を紹介します。
最後までお付き合いをよろしくお願いいたします。それではまた。
Discussion