Closed15

💻 Type challenge / Medium (3 of 10) やっていくスレ

snakasnaka

ReplaceAll

Replace の再帰版だとおもったが、微妙に違うっぽい

type ReplaceAll<S extends string, From extends string, To extends string> =
  From extends ''
    ? S
    : S extends `${infer L}${From}${infer R}`
      ? ReplaceAll<`${L}${To}${R}`, From, To>
      : S

以下のテストが通らない

  Expect<Equal<ReplaceAll<'foobarfoobar', 'ob', 'b'>, 'fobarfobar'>>,
  Expect<Equal<ReplaceAll<'foboorfoboar', 'bo', 'b'>, 'foborfobar'>>,
snakasnaka

なるほど

置換済みの文字列がさらに置換されてるのがダメか

snakasnaka

置換対象を残りの部分 R だけに適用するように調整した

type ReplaceAll<S extends string, From extends string, To extends string> =
  From extends ''
    ? S
    : S extends `${infer L}${From}${infer R}`
      ? `${L}${To}${ReplaceAll<`${R}`, From, To>}`
      : S

${R} はそのまま R で良かったな

snakasnaka

Append Argument

type AppendArgument<Fn, A> = Fn extends (...args: infer Args) => infer Ret
  ? (...args: [...Args, A]) => Ret
  : never

関数の引数を infer する方法がいつも曖昧
...args: infer T の形 identifier: type の形の発展で identifier が複数あると ...identifiers みたいに rest operator (?) のようにまとめて取れる
とおぼえておぼえておこう

逆に tuple から関数の型を組み立てるときは

([...Args, A]) => Ret

ではダメで

(...args: [...Args, A]) => Ret

ここでも ...args: が必要で、無いと A rest element must be last in a destructuring pattern. みたいな、よくわからないことを言われてしまう

snakasnaka

Length of String

tuple の length が型レベルで取得できることを利用する

type LengthOfString<S extends string, T extends string[] = []> = 
  S extends `${infer First}${infer Rest}`
    ? LengthOfString<Rest, [...T, First]>
    : T['length']
  • S extends '${infer First}${infer Rest}' で 文字列から先頭の1文字 (First) と残りの文字列(Rest) を取り出す
  • ? LengthOfString<Rest, [...T, First]> で、再帰的 LengthOfString を適用して、対象の文字列(S)として Rest をわたす
  • 型変数 T は tuple 化した文字列を格納するための変数として利用する
  • S extends '${infer First}${infer Rest}' の条件に合致しないという状況は、文字列の最後まで到達した場合なので、 T['length'] で tuple の長さ ( = 文字列の長さ) を適用する
snakasnaka

Flatten

tuple を [infer First, ...infer Rest] で分解して再帰する方法をでできたけど、もうちょっと整理できそう

type Flatten<T extends [...any]> =
  T extends [infer First, ...infer Rest]
    ? First extends any[]
      ? [...Flatten<First>, ...Flatten<[...Rest]>]
      : [First, ...Flatten<[...Rest]>]
    : T
snakasnaka

少し整理した

type Flatten<T extends [...any]> =
  T extends [infer First, ...infer Rest]
    ? [
        ...(First extends [...any] ? Flatten<First>: [First]),
        ...Flatten<Rest>
      ]
    : T
  • [...any]any[] でもよさそう
  • 最後の : T は、: [] でも結果的には同じになるっぽい
snakasnaka

Append to object

type AppendToObject<T, U extends string, V> = {
  [k in keyof T | U]: k extends keyof T ? T[k] : V
}

一応解けた

  • keyof T | U でオブジェクトの key (k) にセットするものを T の key と U とする
  • 値の型については k にセットされている型を検査して
    • T の key に該当するものであれば T[k] でそのプロパティの型とする
    • そうでない場合は key は U のはずなので V を使用する

最初、以下と等価だと思ったけど違うのかな... ?

type AppendToObject<T, U extends string, V> = T & { [k in U]: V }
snakasnaka

Absolute

文字列に変換する部分を ToS<T> として切り出して考えてみた

type ToStr<T extends number | string | bigint> = `${T}`
type Absolute<T extends number | string | bigint> =
  ToStr<T> extends `-${infer Num}`
    ? Num
    : ToStr<T> 

さほど複雑じゃないのでインライン化してもよさそう

type Absolute<T extends number | string | bigint> =
  `${T}` extends `-${infer Num}`
    ? Num
    : `${T}` 
snakasnaka

ここまでの進捗

Permutation がまだ理解できていないが、一旦次のスレへ

このスクラップは2023/12/09にクローズされました