Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

型レベルでPermutationする

こんな感じの型です

type Fuyu = Permutation<'あんたは' | 'ここで' | 'ふゆと' | '死ぬのよ'>;
// ['あんたは', 'ここで', 'ふゆと', '死ぬのよ'] | ['あんたは', 'ここで', '死ぬのよ', 'ふゆと'] | ['あんたは', 'ふゆと', 'ここで', '死ぬのよ'] | ...

※本記事ではTypeScript beta版の機能を使用しています

作り方

けっこう愚直に書いてます。そこまで説明することもないですね。

type Permutation<T, U = T> =
    U extends string ?
        Exclude<T, U> extends never ?
            [U] :
        [U, ...Permutation<Exclude<T, U>>] :
    [U];

ポイントはTとUの2変数を使っている点です。こうしないと、conditional types でバラされたあとに元々の型がわからなくなってしまい、Exclude<T, U>の部分が実現できません。

気をつけるべきはベータバージョンでしか動かないことです(2020年10月現在)。頑張れば下位バージョンでもできそうですが、まだ思いついていないです。

実例

type Permutation<T, U = T> =
    U extends string ?
        Exclude<T, U> extends never ?
            [U] :
        [U, ...Permutation<Exclude<T, U>>] :
    [U];

type ArrayToString<T extends string[]> =
  T extends [infer head, ...infer tail] ?
    head extends string ?
      tail extends string[] ?
        `${head}${ArrayToString<tail>}` :
      '' :
    '' :
  '';
type Fuyu = ArrayToString<Permutation<'あんたは' | 'ここで' | 'ふゆと' | '死ぬのよ'>>;
const str: Fuyu[] = ['あんたはここでふゆと死ぬのよ', 'あんたはふゆとここで死ぬのよ', 'ここであんたはふゆと死ぬのよ'];

const normalize = (str: Fuyu) => 'あんたはここでふゆと死ぬのよ' as const;

おまけ: 不要なものを除外する

上記の例では「死ぬのよここでふゆとあんたは」のようなものまで含んでしまっています。不要なものを除外するときはExcludeを使います。 

type Fuyu = Exclude<
    ArrayToString<
      Permutation<'あんたは' | 'ここで' | 'ふゆと' | '死ぬのよ'>
    >,
  '死ぬのよここでふゆとあんたは'>;