突発ネタ記事です。Qittaに投げるのが怖かったのでここに投げます。
皆さんはTypeScriptを利用していて、「文字列の配列がしりとりとして成立しているかどうかを型レベルで判定したい」と思ったことはありますか?僕はありません。あるとしたらどこかで何かを間違えています。
しかしこういうことは 判定したい/したくない ではなく、できるからやる が大事です。やりましょう。
やりたいこと
type ShiritoriTest1 = isShiritori<["こぶた", "たぬき", "きつね", "ねこ"]>
// ↑ type Answer = true になってほしい
type ShiritoriTest2 = isShiritori<["真中らぁら", "南みれぃ", "北条そふぃ"]>
// ↑ type Answer = false になってほしい
これを目指します。
完成形
/** 文字列の最初の文字(のリテラル型)を返す型 */
type First<T extends string> = T extends `${infer L}${infer R}`
? (R extends ""
? L
: First<L>
)
: undefined
/** 文字列の最後の文字(のリテラル型)を返す型 */
type Last<T extends string> = T extends `${infer L}${infer R}`
? (R extends ""
? L
: Last<R>
)
: undefined
/** 文字列の配列がしりとりとして成立しているかを判定する型 */
type isShiritori<Words> = Words extends [infer FirstWord extends string, infer SecondWord extends string, ...infer Rest extends string[]]
? (FirstWord extends ""
? false
: (Last<FirstWord> extends First<SecondWord>
? (Rest extends []
? true
: isShiritori<[SecondWord, ...Rest]>
)
: false
)
)
: false
type ShiritoriTest1 = isShiritori<["こぶた", "たぬき", "きつね", "ねこ"]>
// type ShiritoriTest1 = true
type ShiritoriTest2 = isShiritori<["真中らぁら", "南みれぃ", "北条そふぃ"]>
// type ShiritoriTest2 = false
解説
を、しようと思ったのですが、細かい解説は他サイト様の記事に任せて、これらの操作にどんな名前がついているかのみ簡単にまとめます。
TypeScriptの型機能については、型パズルで学ぶTypeScriptの型│FORCIA CUBE│フォルシア株式会社が詳しいのでぜひ一度お読みください。
ジェネリクス
ジェネリクス(Generics)は、(誤解を恐れずに言えば)"型を引数のように扱う"機能です。型定義のみならず、関数定義時やクラス定義時などでも利用できます。
<T extends string>
のように、不等号の対<>
で囲われた部分が"型を引数のように渡している"場所になります。
Conditional Types
Conditional Typesは、その名のとおり、"条件付きの型"を実現する型です。
R extends "" ? L : First<L>
といった形で三項演算子のように記述することで、条件によって型を切り替えることができます。
Template Literal Types
Template Literal Typesは、JavaScriptにおける(通常の文字列としての)テンプレートリテラルの構文を使って、型を記述することができる機能です。
感想
Template Literal Types、恐ろしい子。