TypeScriptの幽霊型、その２

# TypeScriptの幽霊型、その２

幽霊型とは、変数・関数内に使用されないまま、追加情報を提供する型である。用例として、型の不明なデータを確認するデコーダ（解析機）とともに使うと、デコードした型を幽霊型としてデコーダに保管できる。デコードしたデータを変えても、その元の形は型に残る。

前回の記事で紹介したように、幽霊型とデコーダを使い、元々`any`か`unknown`型のオブジェクトからネストされたフィールドの型までTypeScriptに伝えることができた。

しかし、今までのデコーダでは入力と出力がいつも同一型だったが、今回紹介する`map`デコーダは違う。配列の`map`同様、デコーダの`map`はデコードしたデータに変更を加えるために用いるので、出力は入力と異なる値になる。

## デコーダとは何か

まずデコーダとは何か復習しましょう。一番単純なデコーダは、TypeScriptの基本的な型（数字や文字列など）をデコードするだけ。

``` ts
// Result => 結果は値かエラーになることを示す型
// Error => 汎用的なエラー型およびエラーの値
type Decoder<Output, Input = Output> = (input: unknown) => Result<Output, Error>
```

`Decoder<Output, Input = Output>`の場合、`Input`と`Output`は大抵同じ型になると予測し、デフォルトで`Input`に`Output`を代入する。したがって、`Decoder<string>`は`Decoder<string, string>`に等しい。

``` ts
// `ok`と`err`はそれぞれデータとエラーを含む`Result`のコンストラクタ
const stringDecoder: Decoder<string> = (input: unknown) => typeof input === "string" ? ok(input) : err('should be a string')
```

要は、`typeof`を利用し、入力は文字列だということが判明した。

## mapデコーダ

では、文字列をデコードしたが、その文字列を数字に変換したいとする。いよいよ、`map`の出番だ。

#### 第1問

デコードされた文字列を数字に変換するデコーダだとすれば、`map`はどの引数二つをもらわねばならない？

<details>
<summary>`map`の引数</summary>

1. 文字列デコーダ
2. 文字列から数字への変換を行う関数（例えば、`Number`関数）

</details>

その`map`デコーダに文字列を渡せば数字（それともエラー）が戻されるはずだ。

``` ts
map(stringDecoder, Number)(stringInput) => 数字？
```

#### 第2問

`map`の型はなんだ？ヒント：型パラメーターが必要になる。

<details>
<summary>`map`の型</summary>

``` ts
// O1とO2はそれぞれ元のデコーダと最終デコーダの出力の型（O -> Output）
const map = <O1, O2, I = O1>(decoder: Decoder<O1, I>, f: (t: O1) => O2): Decoder<O2, I> => (input: unknown) => ...
```

`f`は値を型`O1`から型`O2`へ変換する関数だ。もちろん、`O1`と`O2`が必ずしも異なる型だとは限らない。例えば、`f`が数字を受け取り、その数字の倍を返す関数なら`O1`も`O2`も`number`である。
</details>

`map`にデコーダを渡すと、出力されるデコーダの出力が違う型を持つ可能性がある。ならば、最終デコーダの入力と出力の型も違う。

これこそ、幽霊型（デコーダ入力の型）の醍醐味だと考える。つまり、`unknown`型の入力から始め、どんな変換を経ても`unknown`の後ろに隠れていた入力の真型を記録することができる。また、この記録は完全に自動的に行われる。

### map関数の本体

``` ts
const map = <T, U, I = T>(decoder: Decoder<T, I>, f: (t: T) => U): Decoder<U, I> => 
	(input: unknown) => 
		Result.map(decoder(input), f);
```

`decoder`の返り値を求め、`f`を適用するだけ。しかし、問題は`Result.map`だ。

`Result.map`の型を見れば、その用途がわかる：

``` ts
// Result.ts
const map = <T, U, Error>(result: Result<T, Error>, f: (t: T) => U): Result<U, Error> => ...
```

`Result.map`は`Result`内の値に関数を適用する。また、`Result`はエラーであれば、そのエラーをそのまま返す。

#### 第３問

`Result`のサブタイプである`Ok`と`Error`は以下の通りならば、`Result.map`関数の本体はどうなる：

``` ts
type Ok<T> = { tag: 'ok', value: T }
type Error<E> = { tag: 'error', error: E }
```

<details>
<summary>`Result.map`関数の本体</summary>

``` ts
const map = <T, U, Error>(result: Result<T, Error>, f (t: T) => U): Result<U, Error> => {
	if (result.tag === 'ok')
	  return { tag: 'ok', value: f(result.value) }
	} 
	// エラーの場合、そのまま返す
	else {
	  return result
	}
}
```
</details>

### mapの用例

最後に、`map`デコーダの用例と入出力の型の例はこちら：

#### デコードしたオブジェクトを文字列に変換

``` ts
const urlObjectDecoder = objectDecoder({
  scheme: stringDecoder,
  host: stringDecoder,
  path: stringDecoder
})

const urlObjectToStringDecoder = map(urlObjectDecoder, ({ scheme, host, path }) => `${scheme}://${host}/${path}`)
```

`urlObjectToStringDecoder`の入力の型：

``` ts
type Input = {
  scheme: string
  host: string
  path: string
}
```

`urlObjectToStringDecoder`の出力の型：

``` ts
type Output =  string
```

``` ts
const urlString = urlObjectToStringDecoder({ scheme: 'https', host: 'www.example.com', path: 'path/to/something' })
```

`urlString`の型は`Input`型と同値なので、`Ok`が戻される：

``` ts
urlString === { tag: 'ok', value: 'https://www.example.com/path/to/something' }
```


## まとめ

今回は入出力の異なるデコーダに幽霊型を適用し、不定形なデータの変化を観察することができた。