Branded Type について理解する

# 目次
1. 背景
2. 既存の問題
3. Branded Type とは
4. ユースケース
5. 実践
6. 最後に

# 1. 背景
先日あった[Sansan様のセミナー](connpass)でBrandedTypeについて初めて知った為です。

こちらが[プレゼンに使われていた資料](https://speakerdeck.com/sansantech/sansan-20230912-3)です。

[元ネタはこちら](https://egghead.io/blog/using-branded-types-in-typescript)です。

次節から元ネタの方を和訳して説明します。

# 2. 既存の問題

以下をご覧ください。
```typescript

function print(age:number,height:number) {
    console.log('ageを表示:',age);
    console.log('heightを表示:',height);
}

const age = 25;
const height = 175;
print(height,age);

```
引数の順番を間違えてしまいましたが、どちらも同じstring型を受けっているのでTypescriptではエラーにはなりません。
しかし、意図した処理にはなってないです。
実行後にこの呼び出しに問題があることが判明してしまいます。

# 3. Branded Type とは
既存の型に属性やラベルを追加した型です。

## 例
```typescript
type BrandType<K, T> = K & { __brand: T }
type AgeType = BrandType<number, "age">
type HeightType = BrandType<number, "height">
```

これはAgeTypeという新しい型を作成し、_brand:"age"というkey,valueのみを持つオブジェクトと関連付けます。
このBrandTypeで宣言された型は、number型であることに加えて_brandプロパティの値と一致することを強制します。

BrandTypeを使って、前の例を書き直してみます。
```typescript
type BrandType<K, T> = K & { __brand: T }
type AgeType = BrandType<number, "age">
type HeightType = BrandType<number, "height">

function print(age:AgeType,height:HeightType) {
    console.log('ageを表示:',age);
    console.log('heightを表示:',height);
}

const age = 25 as AgeType;
const height = 175  as HeightType;

print(height,age);
/**Argument of type 'number' is not assignable to parameter of type 'AgeType'.
  Type 'number' is not assignable to type '{ __brand: "age"; }'. */
```
[playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBAQgTgQwHYBMAq4IB4DSAaKNAPigF4ocoAyKAbygH0GAjRVALkKgF8AoUSFACCAcwgZB5eMnSYsSAK4BbZhDgEARAjEai-TFAASEAJYiAFsAnQpbWZHnLV6qBvOmLwXb14AzBUgAxsAmAPZIUGBwJkjAABTaEOyi4ph47maW7MaZVpgAlHS8UCVQgeEAzqEANhAAdNWhInEA5ImASQyAFhGAXJ7sLXiJ+QDcxaXlSFW1DU2tGZ5dvf1zlsO8fLzjFcBQiWRQAEwArDsVwmLWI5vby9vkAIwA7McIpzmeFz5RMfE3A2KrAHoAFRAoRwETKCCxKChXxQATQFqKFRqFpQEynJChbYvCpmJAIZi1eGhSIIRBKCDANQwuEIqAtFLWFp1UbWBnI5xojFQLE4ip4kQEonQYCk+ktehMVgyThaHRDHgsqBAgFAA)

しかし、まだこれだけだと問題があります。
型を "タグ付け"するために使われる__brandプロパティは、"ビルド時"のみのプロパティです。
このプロパティを参照する処理を書くと、実行時にこのプロパティが存在しないため、undefinedになって問題が発生する可能性があります。

```typescript
type BrandType<K, T> = K & { __brand: T }
type AgeType = BrandType<number, "age">
type HeightType = BrandType<number, "height">

function print(age:AgeType,height:HeightType) {
    console.log('ageのbrandedプロパティを表示:',age.__brand);
    console.log('heightのbrandedプロパティを表示:',height.__brand);
}

const age = 25 as AgeType;
const height = 175  as HeightType;

print(age,height);
```

実行結果
```
"ageのbrandedプロパティを表示:",  undefined
"heightのbrandedプロパティを表示:",  undefined
```
[playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBAQgTgQwHYBMAq4IB4DSAaKNAPigF4ocoAyKAbygH0GAjRVALkKgF8AoUSFACCAcwgZB5eMnSYsSAK4BbZhDgEARAjEai-TFAASEAJYiAFsAnQpbWZHnLV6qBvOmLwXb14AzBUgAxsAmAPZIUGBwJkjAABTaEOyi4ph47maW7MaZVpgAlHS8UCVQgeEAzqEANhAAdNWhInEA5ImAdgysMhAogOsMgLcMgIsMgGMMgMUMgEkMgBYRgFye7C14iXVMXaj5ANzFpeVIVbUNTa0Znp12PQMjEzNz6R6WSyx267x8vNsVwFCJZFAATACsnwqwjE1g2bw+R0s3wAjAB2AGAoy3PKQDa8KIxeKJG65dZAA)

次に改良されたBrandedTypeを紹介します。
```typescript
declare const __brand: unique symbol
type Brand<B> = { [__brand]: B }
export type Branded<T, B> = T & Brand<B>
```
[unique symbolについてはこちらをご覧ください](https://qiita.com/yamatai12/items/39648d883b917e7a05da)
[declareについてはこちらをご覧ください](https://qiita.com/yamatai12/items/eb271421a6c3ba367624)
- 「__brand」という名前のハードコードされたプロパティを使う代わりに、計算されたプロパティを使うことができます。
- 前述のプロパティの重複を避けるために、ユニークなシンボルを使用することができます。
- プロパティへの読み取りアクセスを禁止します。

```typescript
declare const __brand: unique symbol
type BrandType<B> = { [__brand]: B }
export type BrandedType<T, B> = T & BrandType<B>
```

```typescript
type AgeType = BrandedType<number, "age">
type HeightType = BrandedType<number, "height">

function print(age:AgeType,height:HeightType) {
    console.log('ageを表示:',age);
    console.log('heightを表示:',height);
}

const age = 25 as AgeType;
const height = 175  as HeightType;

print(height,age);
/**Argument of type 'HeightType' is not assignable to parameter of type 'AgeType'.
  Type 'HeightType' is not assignable to type 'BrandType<"age">'.
    Types of property '[__brand]' are incompatible.
      Type '"height"' is not assignable to type '"age"'. */
```
[playground](https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEYD2A7AzgF3gfWwIxihWAC54BXFASwEdyE0BPAWzyQgCgNGAHBAIULEAKrxAAefgD54AXngBveAG1cBIsAC6ZfvAC+XMfEEaQwUX3HCANMZnzh8AGTGh5sZKkdDfeAEEAcxALBHkTYjMQ8RRyVhAYWwAiKCDEr25fAAkQKgCACwwQuVdTd0sYuIT4RLyc-Iw07wAzSjAMKlR4HhgqFAwAChSQEkDgsWta3IKSbKnCsQBKRQ54VcRUNHYQADoIJAD+gHIhwDsGdQjgQHWGQFuGQEWGQDGGQGKGQCSGQAsIwC5PEkPrIYWAbhWa2Q6C2u32R0m9TObjMNweLw+XwmdQK-w4Bg4wMw8CGxQATABWHFofxBEIArFYSEFYoARgA7ETifBZvVyd5ur0BtSMD8gmiAPQAKiFfhgAViID68CQTXgGQQh1ZBRCh3gVBJKCQWCgaDQuRQUDwEAQGCQXVgUGYIAw8RlcoV8EOo1V20BRSVKPmfDVGvgWp1eoNRpN8vNjsO4TKEmSqSkhzda3gIRJsq6MCQfBg3Cdqnwbk0atgCF6yGYPCg7WNO0BSY9NS9iV9mu1xP1AUN1bD8qMh1jICb23gQoFQA)
上のように[__brand]プロパティの型に互換性がない為、エラーになります。

:::note info
ここで一つ個人的に引っかかります。
ブランドプロパティにアクセスできてしまうという課題をこのplaygroundですと確認できません。
上のコードを各ファイルに分けたものが以下です。
cloneしてコメントアウトしてエラーになるか確認して見てください。
[コード](https://github.com/yamatai12/sample-branded-type)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405182/ecfa4e1a-aa62-25c8-6d8f-a96e48f887aa.png)
確かに[__brand]プロパティにアクセスできないことを確認できました。
:::

# 4. ユースケース
## カスタムバリデーション
```typescript
type EmailAddress = Brand<string, "EmailAddress">
function validEmail(email: string): EmailAddress {
    // email address validation logic here
    return email as EmailAddres;
}
```
メールアドレスが適切なフォーマットである場合にEmailAddress型で返します。

## ドメインモデル
```typescript
type CarBrand = Brand<string, "CarBrand">
type EngineType = Brand<string, "EngineType">
type CarModel = Brand<string, "CarModel">
type CarColor = Brand<string, "CarColor">
```
自動車の製造ラインでは、異なる機能や種類の自動車があります。

```typescript
function createCar(carBrand: CarBrand, carModel: CarModel, engineType: EngineType, color: CarColor): Car {
  // ...
}
const car = createCar("Toyota", "Corolla", "Diesel", "Red") // Error:
// "Diesel" is not of type "EngineType"
```

## APIのレスポンスとリクエスト
```typescript
type ApiSuccess<T> = T & { __apiSuccessBrand: true }
type ApiFailure = {
  code: number;
  message: string;
  error: Error;
} & { __apiFailureBrand: true };
type ApiResponse<T> = ApiSuccess<T> | ApiFailure;
```
APIコールの成功と失敗を区別するために、特定のAPIでブランドを使用しています。

```typescript
const response: ApiResponse<string> = await fetchSomeEndpoint();
if (isApiSuccess(response)) {
  // handle success response
}
if (isApiFailure(response)) {
  // log error message
}
```

## 5. 実践
以下を作成してください
- 人の年齢のための新しいブランド型
- 数値入力を受けてそれをAgeとして返す関数createAge
- およびAgeを受け取って数値を返す関数getBirthYear
- 0<=Age<=125

```typescript
/** You can work on this challenge directly in the typescript playground: https://tsplay.dev/WzxBRN*/
/** For this challenge you need to create the Branded utility type  */
type Age = Branded<never, "Age">; // Replace the never with the corresponding primitive type
/**
 * 🏋️‍♀️ Fill the function createAge, it should accept a number as input and return a branded Age 
 **/
function createAge() {
  // Perform logic and return Age
}
/**
 * 🏋️‍♀️ This function should accept a branded Age type and return a number
 */
function getBirthYear() {
    // Perform logic and return Age
}
/** Usage **/
const myAge = createAge(36); // Should be ok
const birthYear = getBirthYear(myAge)  // Should be ok
const birthYear2 = getBirthYear(36)  // This should show an error
/** Type Tests */
import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
    Expect<Equal<typeof myAge, Age>>,
    Expect<Equal<typeof birthYear, number>>,
    Expect<Equal<Parameters<typeof createAge>, [number] >>,
    Expect<Equal<Parameters<typeof getBirthYear>, [Age] >>
]
```

[動作確認用のplyaground](https://tsplay.dev/WzxBRN)

[回答です](https://www.typescriptlang.org/play?#code/PQKhAIDEHsCdwC4AsCWBncBjJBDANngKYB2A5oeAJ7QCu4xhhAJotFrITghchQEKwcxJs3A0EKPCgSVElAA4VwIYACgRmPDg5ZoxNAnAB9IwCNBwgFxjiKAI40KaSgFtT0PKpmLwAoUwAVBUIAHj4APnAAXnAAb3AAbRNzfwBdaz5wAF8vYN8LEUDgkICAGl9ImIDwADJ8-yDFMPDVXJ8AQXJo+uFmRtDiGjdCWHKAIk7CMfCAbnBgYHAAJUJ5LUweJAoGADcR8AB3aSRELd1YDjR5PSYUMnB5WBQXaRQ9uUVW0BBVZXBAHg3ANP7gHg-wCwBIABMmBUEkeFOFAAZjRiJgJHp2JxuJNytJwGgkLQ8CwcJgNvJDDh6ENTPscBg7vJxOB-OAOAgaLBiEzwCleixJuBfmA1IjkajOZgOFxCJMABQ4ciWQbDWAASks-Niv3miwACiN4XAXOA8NBSChMEzhCzCGyOeBJlqUPC5eRwgAGWp1eUUEIARgATABWFXW22c71MjAO8CnWDQA70QgJgCiFzgMoA5ABJZFwDgo8A7fCODMq1Q5VTfQUAkEQqEBVAYEUolBovEEokk1bk7kFUT87wUZms9nhynKwXCpEttHkBB8FCwZAATU4sBdhHV5DVSup8E1MZjC3AetgBtgRpNZotw5to-t5C1MZHdplDATABEpTKVSqAHRzpANAEKu2g-uAAC0TKPhW3zgAAqmgEZCq0mB6AY4AuJQ-IxBKGLSuQMoAMwAGwqnMx4AMr4sBLDUuA0AANaqGh+iGKYi4rmu3RzguS5IKB65YZMIbauA1EdtyFBMah6HsZxAlrv6PE2nxXFgaRonHg26C4jRhJ6fGlrgCMcawK0lZgOA-TWYQBgYCoqjPNcS4fBQ8TJg4+DlMmAAeigFlk4DwnGRoZgAAoOEHYPgRBkHZwDiJIaAZq0g5YLSdndAkT7gH5AUICEnk0PgISDtA8KYdh5DlJM4ThKUuX5YQKJFV5eBlcEFXcgpgnlLuIz1Y1h55f5LWFcVpU6toOAuDaIxoJ1ijdXhUp1eUCQDbAqTgENTVja1k0ddNghzdwsCLeVlW8b1a4NYkkw7fVqipEAA)

# 6. 最後に
BrandedTypeはprimitive型の型安全性を高めるのに役立つと思いました。

# 7. 参考
- [TypeScript でも Nominal Typing がしたい](https://qiita.com/takayukioda/items/32de7f05b7dd9e025ce7)
- [Nominal typing techniques in TypeScript](https://michalzalecki.com/nominal-typing-in-typescript/)
- [node-starter-project](https://khalilstemmler.com/blogs/typescript/node-starter-project/)

## 参考になる記事を書いてくださった皆様ありがとうございました！

# 宣伝させてください！
https://recruit.iimon.co.jp/