LoginSignup
12
4

More than 1 year has passed since last update.

TypeScriptでBranded Typeを利用してひらがな型を扱う

Last updated at Posted at 2020-08-27

TypeScriptは基本的には構造的型(Structural type system)なので

type Human = {
 name: string
 age: number
}

type Dog = {
 name: string
 age: number
}

を型として区別する事が少なくとも現時点ではできません。構造が同じであれば同じ型と見做すからです。なので例えばstring型と同じ構造をもった別の型としてHiragana型を扱おうとして

type Hiragana = string

const charIsHiragana = (char: string) char is Hiragana => {
  if (char.length > 1) {
    return false
  }
  const codePoint = char.codePointAt(0)
  return (codePoint >= 12353 && codePoint <= 12435)
}

const hiraganaToKatakana = (hiragana: Hiragana): Katakana => {/* 中略 */}

const charToHiragana = (char: string) => {
  if (charIsHiragana(char)) {
    // charがHiragana型の時は許される
    return hiraganaToKatakana(char)
  } else {
    // charがHiragana型じゃない時は型検査で許されないで欲しいが、string型であれば構造が一致するので許されてしまう
    return hiraganaToKatakana(char)
  }
}

みたいに書いてもそもそもType AliasにしかならずHiraganaとstringを区別できないため型検査上意図した動きにはなりません。こういう時の解決方法の一つは型上の構造(実体ではなく)を変え、アサーション(やTypeGuard)を利用する事で別々の型として扱ってもらうという方法です。

// 他に存在しないプロパティ名を型上に生やす
type Hiragana = string & { _hiraganaBrand: never }
type Katakana = string & { _katakanaBrand: never }

// 共通した名前のプロパティを持っていてもそのプロパティの型が異なれば別々の型として扱われる
// 文字列リテラル型でもok
type Hiragana2 = string & { _brand: 'Hiragana' }
type Katakana2 = string & { _brand: 'Katakana' }

運用でカバー的な泥臭さは感じますがこのbrandingを利用すれば、本来の意図通りひらがな(charIsHiraganaを通過したstring)以外が誤ってhiraganaToKatakanaへ渡される事を防げます。また広く使うなら

type Brand<K, T> = K & { __brand: T }
type Hiragana = Brand<string, 'Hiragana'>

みたいなBrand型を用意してしまうのも良いんじゃないでしょうか。

see also

12
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
4