初めてでもこわくない！TypeScriptで関数型プログラミングをしてみよう

# <font color="red">この記事は「技育祭」というイベントで発表したものです</font>

---

# <font color="red">是非「スライドモード」でご覧ください</font>

---

# みなさん、凶悪な関数はお好きですか？

---

# 最近弊社では「ゆめみからの挑戦状」というクイズ企画をTwitter上で行なっています

---

# その企画の中で、こんな問題を出したことがあります

---

# 「足し算関数に1行追加して、凶悪にしてください」

---

# 元となる足し算関数

```diff_typescript:TypeScript
  const add = (a: number, b: number): number => {
    // ここに1行追加して、凶悪な関数にしてください。
    return a + b
  }
```

---
# すると、たくさんのエンジニアさん達が<br>さまざまな凶悪な処理を考えてくれました

---
# いくつか見ていきましょう

---

```diff_typescript:TypeScript
  const add = (a: number, b: number): number => {
+   if (Date.now() >= Date.parse('2023/01/01')) return 0;
    return a + b
  }
```

---
# 2023年になると、`0`を返すようになる関数です

---
# これは凶悪ですね・・・！

---
# 2022年の間はちゃんと足し算をしてくれるので

---
# 自動テストなんかもすり抜けてしまいます・・・！

---
# もう一つ見てみましょう

---

```diff_typescript:TypeScript
  const add = (a: number, b: number): number => {
+   process.env.ENV = "DEV";
    return a + b
  }
```

---

# 足し算をすると、<br>環境変数を上書きしてしまう関数です

---

# 本番環境なのに、足し算をしたら<br>開発環境モードの動作に変わってしまうんでしょうか

---

# 恐ろしいですね・・・！

---

# エンジニアの皆さんは、<br>凶悪なことを考えるのが本当にお得意でいらっしゃいます・・・！

---

# このような凶悪なコード、<br>大喜利の解答として見ている分には面白いんですが

---

# 「実際にプロダクトのコードに紛れ込んでしまったら」<br>と考えると、恐ろしくてたまりません

---

# 間違って凶悪な処理を書かないように、<br>気をつけないといけませんね

---

# でも、気をつけなくても──

---

# これらの凶悪な処理をそもそも書けない──

---

# そんなプログラミング言語もあります

---

# その一つが、純粋関数型言語Elmです

---

# Elmでは「参照透明」な関数しか書けません

---

# 参照透明とはなんでしょうか？

---

# まずは先ほどのTypeScriptの足し算関数を見てみましょう

---

```diff_typescript:TypeScript
  const add = (a: number, b: number): number => {
    if (Date.now() >= Date.parse('2023/01/01')) return 0;
    return a + b
  }
```

---

# この関数は、例えば`3`と`5`を渡したら`8`を返してくれます

---

# でも、2023年になると正しい答えを返さなくなります

---

# どんな引数を渡しても`0`を返すようになってしまいます

---

# 現在時刻によって、返す値が変わってしまう関数です

---

# 抽象的には「外部の状態に依存する関数」です

---

# `3`と`5`を入れたら、必ず`8`を返してほしいですよね？

---

# 2023年になっても、いつまでも`8`を返してほしいものです

---

# `3`と`5`を入れたら、必ず`8`を返してくれる

---

# 同じ引数を渡したら、必ず同じ結果を返してくれる

---

# その性質を「参照透過性・参照透明性」と言います。

---

# でも、先ほどの凶悪な足し算関数は<br>日時に依存しているため、参照透明ではありません

---

# 参照透明だと、何が嬉しいのでしょうか？

---

# 結果が予測しやすい

---

# 参照透明な関数は、<br>同じ引数を渡せば、いつも同じ答えを返します

---

# 場面によって返す答えが変わったりしません

--- 

# なので、挙動が予測しやすく、<br>自動テストとの相性も良いです

---

- 3と5を渡したら、8が返ってきたね！
- 1と10を渡したら、11が返ってきたね！

↑こんな風に、自動テストで動作を保証できます

---

# 参照透明でない関数は、テストしづらかったりします

---

# 先ほどの、日時に依存している関数とかですね

---

# 同じ引数を渡しても、<br>場面によって返ってくる値が変わるためです

---

# ただ、テストできなくもないです

---

# JavaScriptのDateをモック化してくれる<br>ライブラリなどもあります

---

# もしくは──

---

# 2022年 大晦日

ワイ「ついにこの日が来たで・・・！」
ワイ「今、23時30分や・・・」
ワイ「そろそろ**年越しテスト**を開始するで！」

---

ワイ「`npm run test`！」
ワイ「`npm run test`！」
ワイ「`npm run test`！」
ワイ「`npm run test`！」

---

# 2023年、到来
ワイ「お、年越した？年越した？」
ワイ「年越したな！」
ワイ「`npm run test`！」
ワイ「お、戻り値が変わった！」
ワイ「よっしゃ！テスト成功や！」

---

# 「年越しのタイミングで、ちゃんと関数の戻り値が変わったで！」

---

# きっと、一生の思い出に残るテストになると思います

---

# 話を戻します

---

# 参照透明な関数には、ほかにもメリットがあります

---

# どんなに重い計算でも、引数が分かればメモ化できます

---

# 外部の状態を気にしなくてよいので、再利用性も高いです

---

# 2つめの凶悪な関数についてはどうでしょうか

---

```diff_typescript:TypeScript
  const add = (a: number, b: number): number => {
+   process.env.ENV = "DEV";
    return a + b
  }
```

---

# 計算の結果を返せばいいだけなのに、<br>外部の状態を変えてしまっています

---

# 関数が外部の状態を変えたり、<br>外部に影響を与えたりすることを「副作用」と呼びます

---

# 逆に、副作用がなく参照透明な関数は<br>「純粋関数」と呼ばれます

---

# 純粋関数型言語であるElmでは、皆さんの書くコードが<br>純粋関数となるように設計されています

---

# 同じ引数を渡せば、必ず同じ答えが返ってきます

---

# じゃあ、場面によって答えを変えたい場合はどうすればいいの？

---

# 例えば「今日は○月○日です！」という<br>文字列を生成してくれる──

---

# そんな関数を作りたい場合はどうすればいいの？

---

# そういう関数は、日によって返す値が違うはずじゃん！

---

# 純粋関数しか書けなかったら、<br>そういう関数が作れないじゃん！

---

# そこは、ちょっと不思議な仕組みで可能にします

---

Elmという言語では、

1. 現在の日時を取得する
2. その日時を元に「今日は○月○日です！」という文字列を生成して、返す

↑これを、**1つの関数の中で**行うことが**できません**。

---

ではどうするのかというと、

1. 「現在の日時を取得してくれい！」というお手紙をElmランタイムに渡す
2. Elmランタイムから、現在の日時が渡ってくる
3. 受け取った日時を元に「今日は○月○日です！」という文字列を生成して、返す

↑このように、どこかで「Elmランタイムにお願いする」フェーズを挟みます

---

そのため、1つの関数の中で

1. 現在の日時を取得！
2. その時刻を元に文字列を生成して、返す！

↑これができません

---

# 「現在の日時を取得して、メッセージ文を生成して、返す」<br>という関数が書けないのです

---

# でも、Elmランタイムにお願いして<br>現在の日時を渡してもらうことはできるので

---

# 結果的に「今日は○月○日です！」という<br>文字列を生成することはできます。

---

# 日時に依存するような処理も<br>純粋関数だけを組み合わせて実現できる──

---

# ちょっと不思議な仕組みです

---

TypeScriptの場合は、以下のようにすると良いと思います。

```diff_typescript:TypeScript
// 「日時に応じたメッセージを作ってくれる関数」 を実行
const dateMessage = createDateMessage(Date.now());
```

関数の中で日時を取得するのではなく、
引数として日時を渡す感じです。

---

# こうすることで関数が参照透明になり、<br>自動テストがしやすくなります

---

# 引数として渡す日時を色々変えたりして<br>テストをすることができますね

---

# TypeScriptでも、意識すれば関数型っぽく書けそうです

---

# では、乱数を扱う場合はどうでしょうか？

---

# 純粋関数だけで、できるのでしょうか？

---

# Elmでは、乱数なども似たような方法で扱います


---

1. 乱数を生成！
2. その数値を使って計算し、結果を返す！

↑これを1つの関数の中で行うことができません

---


どうするかというと、

1. 「乱数を作ってくれい！」というお手紙をElmランタイムに渡す
2. Elmランタイムから、生成した数値が渡ってくる
3. 受け取った数値を元に計算し、値を返す

↑やはり、どこかで「Elmランタイムにお願いする」フェーズを挟みます

---

# そのため「実行するたびに違う数値を返す関数」は<br>書けないのですが──

---

# ランタイムにお願いして乱数を作ってもらって、<br>それを受け取って画面に表示する

---

# ということはできます

---

# サイコロを表示するWebページなんかも作れます

---

# 参照透明な関数だけを組み合わせて、<br>乱数を扱えます

---

# Elmでは、他にも凶悪な処理が書きづらくなっています

---

Elmでは、

```diff_typescript:TypeScript
  const add = (a: number, b: number): number => {
+   process.env.ENV = "DEV";
    return a + b
  }
```

↑こういった、勝手に上書きしちゃう関数も書けません

---

# 全ての値がイミュータブル（不変）です

---

# 再代入はできません

---

# 再代入という概念がありません

---

# なので、上書きもできません

---

# 「足し算を実行したら、知らん間に環境変数が変わっとった！」

---

# といった副作用も起こりません

---

# このように、純粋関数型言語では──

- 参照透明でない関数
- 副作用を起こす関数

を書けないため、
冒頭で紹介したような**凶悪な関数**が書けません

---

# これは、言語仕様上そうなっているからです。

---

# 「全てを予測可能にしたい！」という思想のもとで<br>言語が作られているからです

---

# 詳しくは「Elm Guide」でググってみてください

---

# とても分かりやすい、日本語版の公式ドキュメントがあります

---

# でも──

---

# TypeScriptでは「再代入」もできますし

---

# 「1つの関数の中で、乱数を生成しつつ計算」も<br>できちゃいますよね？

---

# なので、凶悪な副作用も普通に起こせます

---

# 参照透明ではない、<br>ころころ結果が変わる関数も書けちゃいます

---

# じゃあ「TypeScriptで関数型プログラミング」とか<br>考えてもしょうがなくない？

---

# いえ、むしろ逆です

---

# TypeScriptでこそ、<br>関数型プログラミングを意識するメリットがあります

---

# いかに純粋関数とそうでない関数を分離できるか、を<br>意識することが大事です

---

# さっきのコードのような<br>「そもそも必要のない処理」を取り除くことも大事です

---

# ところで皆さん

---

# `undefined`や`NaN`はお好きですか？

---

# TypeScriptでは、<br>tsconfigで`strict: true`に設定していても

---

# 気づかないうちに<br>`undefined`や`NaN`が入り込んでしまう場合があります

---

# （2022年10月現在）

---

# すると、予期せぬエラーが発生してしまうことがあります

---

# 「`User`型の値を扱っているつもりが」<br>「いつの間にか`undefined`になってて」<br>「ブラウザでエラーが出てもうた！」

---

# そんなことが起きてしまうことがあります

---

# しかも、いちいち気をつけて`undefined`や`NaN`を<br>チェックするのも大変ですよね

---

# 今日からは `Option<T>` という型を使うことにしましょう

---

# この型を実装するのは面倒なので……

---

# 今回は **fp-ts** というライブラリを使います

https://github.com/gcanti/fp-ts

---

# 「ユーザーが入力した文字列を、数値に変換する」<br>という例を考えてみます

---

TypeScriptだと

```diff_typescript:TypeScript
const num: number = Number("5")
```

または

```diff_typescript:TypeScript
const num: number = parseInt("5")
```

こうですね

---

# でも──

---

```diff_typescript:TypeScript
const num: number = Number("あああ")
```

こうすると、変数`num`の値は`NaN`になってしまいます

---

# しかも、`NaN`は`number`型の一種という扱いなので

---

# コンパイルエラーで気づけません

---

# 普通に`number`型の変数に代入できてしまいます

---

# 明らかに例外的な値なのに、です

---

# （2022年10月現在）

---

# そのため──

---

# 画面に`NaN`って表示してしまったりするミス、<br>たまに起こります

---

# 私もこの間やらかしました

---

# せっかくTypeScriptを使っているので

---

# ブラウザ上でチェックして、<br>初めて変な値に気づくのではなく

---

# 事前にテキストエディタ上で、<br>コンパイルエラーで気づきたいですよね？

---

Elmという言語の場合は、

```diff_elm:Elm
int = String.toInt("5")
```
↑このようにして文字列を数値に変換します。

---

# でも、直接`Int`型の値には変換できません

---

```diff_elm:Elm
int = String.toInt("5")
```

↑この関数の戻り値は`Maybe Int`型になります。

---

# 文字列から数値に変換する訳なので──

---

# できないこともあります

---

# 数値に変換できない例

```diff_elm:Elm
int = String.toInt("あああ")
```
---

# なので`Maybe Int`型な訳です

---

# `Maybe Int`型の値は、<br>そのまま計算に使ったりできません

---

# 数値に変換できてるかもしれないし──

---

# できていないかもしれない──

---

# そんな「シュレディンガーの猫」みたいな値になります

---

# `Maybe Int`型の値を計算などに使いたい場合には

---

# 必ず「数値に変換できてなかった場合の処理」を<br>書かないといけません

---

# 例外的な値が発生しそうな処理をする際には

---

# 「例外的な値が発生したらどうするか」を書いておかないと

---

# コンパイルエラーが起こって先に進めません

---

# 「全てを予測可能にしたい」

---

# 「例外的な値も、事前に予測して対処したい」

---

# そんな「関数型っぽさ」がよく現れた型ですね

---

# こちらも、詳しくは「Elm Guide」を読んでみてください

---


# fp-ts の話に戻ります

---

# fp-ts の`Option<T>`も、先ほどの`Maybe`と同じような型です

---

# 「Option」という言葉には<br>「必須ではない」という意味があります

---

# つまり「存在しないかもしれない値」を型で表現できます

---

# 文字列を数値に変換する例を見てみましょう

---


# fp-ts には`fromString`という関数が用意されています

---

# 文字列を数値に変換するための関数です

---

```diff_typescript:fp-ts
import { fromString } from 'fp-ts-std/Number'
import { Option } from 'fp-ts/Option'

const num: Option<number> = fromString("5")
```
---

# この関数は`Option<number>`型の値を返します

---

# `Option<number>`型の値は<br>`Maybe`と同じように、そのまま計算には使えません

---


# `fromString`という関数の実装はこんなイメージです

```diff_typescript:fp-ts
import { Option, none, some } from 'fp-ts/Option'

const fromString = (value: string): Option<number> => {
    const num = Number(value)
    // NaNになっていないかチェック！
    return Number.isNaN(num) ? none : some(num)
}
```

---

# ちゃんと数値に変換できたかどうか、<br>`NaN`が発生してないかどうか

---

# そこをチェックしてくれます

---

# そして「数値に変換できてなかった場合にはどうするか」を<br>ちゃんとコードで書かないといけません

---

# そのため「`NaN`のまま計算してもうた！」<br>というミスが起こりません


---

# では`"5"`という文字列を<br>数値に変換する例を見てみましょう

---

# ここでは fp-ts の `pipe`という関数を使っていきます

---

# `pipe`関数の例
```diff_typescript:fp-ts
import { pipe } from 'fp-ts/function'

const num: number = pipe(
    1,
    a => a * 2,
    a => a * 2,
    a => a * 2,
)
```

`pipe`関数を使うと、例えば
`1`という数値に、複数の関数を連続して適用できます。

---

# では`"5"`という文字列を数値に変換しつつ<br>色々な処理を繋げてみましょう

---

# まずは`"5"`という文字列を数値に変換してみましょう

---

# 文字列から数値に変換・・・？

```diff_typescript:fp-ts
import { pipe } from 'fp-ts/function'
import { fromString } from 'fp-ts-std/Number'

const num: number = pipe(
    "5",
    fromString,
)

// -> コンパイルエラー！
```

---

# このコードは、コンパイルエラーが起こります

---


# 文字列から数値への変換は、失敗するかもしれないからです

---

# 「数値に変換できなかったらどうするか」を書かないと、<br>`number`型の変数には代入できません

---

# `Option<number>`のままでは、<br>`number`型の変数には代入できません

---

# 「数値に変換できなかったらどうするか」を明記

```diff_typescript:fp-ts
import * as O from 'fp-ts/Option'
import { pipe } from 'fp-ts/function'
import { fromString } from 'fp-ts-std/Number'

const num: number = pipe(
    "5",
    fromString,
+   O.match(
+     () => { throw new Error("不正な値です!") }, // 変換できなかったケース
+     (value) => value, // 変換できたケース
+   )
)
```

---

# 「不正な値のときには例外を投げる」と明記することで、`number`型に代入できました

---

# TypeScriptの`Number()`関数だと

戻り値が`NaN`かもしれないのに、型としては`number`になるため、
そのまま`NaN`を使った計算をしてしまう恐れがあります。
コンパイルエラーで気づくことができません。

---

# fp-tsの `Option<T>` 型を使うと

戻り値の型を`Option<number>`にできるため、
そのまま計算などができません。
型エラーで事前に気づくことができます。

---

# 実行時に初めて<br>「画面に`NaN`て表示されてるやん！」<br>と気づくのではなく

---

# テキストエディタ上で<br>「お、`Option<number>`やから」<br>「そのまま計算はできひんのやな」<br>と気づくことができます

---

# 「数値に変換できてなかった場合の処理も」<br>「ちゃんと書いとかんとな！」<br>と気づくことができます

---

# また、手動で`NaN`チェックするよりも<br>「他の関数と組み合わせやすい」です

---

# 文字列から数値に変換したあと<br>「さらに二乗する」例を見てみましょう

---

# 文字列を数値に変換して、さらに二乗する例


```diff_typescript:fp-ts
+ // 数値を二乗してくれる関数
+ const square = (value: number): number => value * value

const num: number = pipe(
    "5",
    fromString,   // 文字列から Option<number> に変換！
+   O.map(square), // square関数を使って、値を二乗！
    O.match(
      () => { throw new Error("不正な値です!") },  // 途中で失敗したケース
      (value) => value, //　上手く行ったケース
    ),
)
```

---

# `square`関数は、数値にしか適用できません

---

# でも`O.map()`関数を使うことで、<br>`Option<number>`に対しても適用できるようになります

---

# 「存在しないかもしれない数値」を二乗するようなことが<br>できます・・・！

---

# 不思議ですね・・・！

---

# `O.map()`関数が、<br>`square`関数を変身させてくれるからです

---

# `number`用の関数を、`Option<number>`用の関数に<br>変身させてくれます

---

# これを「関数を持ち上げる」なんて言います

---

# 「数値に変換できてないかもしれない値」に対しても<br>「数値用の関数」を適用できる──

---

# そんな便利な仕組みが用意されているわけですね

---

# でも、まだ先ほどのコードはイマイチです

---

# さらに「`Error`が発生する可能性があるかどうか」も<br>型で扱えるようにして行きます

---

# そのために`Either<A, B>`型を使ってみましょう 

---

# これは `A` か `B` のどちらか一方の値を持つ型です

---

# 今回は「`number`または`Error`」ということを<br>型で表現してみましょう

---

# 「エラーかもしれないし、普通の値かもしれない」<br>そんな値を返す関数

```diff_typescript:fp-ts
import { pipe } from 'fp-ts/function'
import * as O from 'fp-ts/Option'
import { Either, left, right } from 'fp-ts/Either'

const optionToEither = <T>(optionValue: O.Option<T>): Either<Error, T> => pipe(
    optionValue,
    O.match(
        () => left(new Error("不正な値です！")),
        (value) => right(value),
    ),
)
```

---

# `Either` も `Option` のように<br>どちらの値をとるかで分岐する必要があります

---

# 「エラーだった場合、そうじゃなかった場合」を明記しないと<br>コンパイルが通りません

---

# では「エラーになるかもしれない」処理を繋げてみましょう

---

# まず、文字列を数値に変換しつつ二乗して<br>「エラーかもしれない」値を作成
```diff_typescript:fp-ts
import * as E from 'fp-ts/Either'

const errorOrNum: E.Either<Error, number> = pipe(
    "5",
    fromString,    // string -> Option<number>
    optionToEither, // Option<number> -> Either<Error, number>
    E.map(square),  // Either<Error, number> -> Either<Error, number>
)
```

---

# ここでも`E.map()`を使って<br>`square`関数を変身させています

---

# 「エラーかもしれない値」に対しても<br>「数値用の関数」を適用できています

---

# そして、値に応じたメッセージを作成

```diff_typescript:fp-ts
const message: string = pipe(
    errorOrNum,
    E.match(
      (left: Error) => left.message, // 途中でエラーが起きたケース
      (right: number) => `2 乗した値は ${right} です`, // ちゃんと計算できたケース
    ),
)

console.log(message)
```

---

# ここでも、エラーが起きた場合の処理を明記しないと<br>コンパイルが通りません

---

# このように fp-ts をうまく活用することで

---

# `undefined`、`NaN`、例外をとるかもしれない値などを<br>型に落とし込むことができます

---

# 型的に矛盾のあるコードを書いている場合は<br>コンパイルエラーで気づくことができます

---

# 「テキストエディタ上で、事前に気づける」という<br>TypeScriptのメリットを、より強化できました

---

# しかも「存在しないかもしれない値」が発生しても、<br>その後の計算を繋げることができています

---

# 「全てを予測可能にしたい」

---

# 「例外的な値も、事前に予測して対処したい」

---

# 「しかも、いろんな処理を便利に繋げたい」

---

# そういった関数型の思想を、<br>TypeScriptにも取り込むことができました

---

# まとめ

---

# なんだかんだ、TypeScriptって良いと思います

---

# JavaScriptに比べて、だんぜん型安全ですし

---

# `undefined`絡みのエラーも<br>けっこう事前検知できるようになっています

---

# 何より、ユーザー数が多く<br>ライブラリやフレームワークも豊富です

---

# Vue も React もあります

---

# なので、TypeScriptを使いたい場面って多いです

---

# 「でも、もっと関数型言語の思想も取り入れたい」

---

# そんなときは fp-ts 使ってみてください

---

# ※先に関数型言語を少し触ってみてからの方が<br>分かりやすいかもです

---

# 〜完〜

---

# スペシャルサンクス

- 一緒に資料を作ってくれたsiketyan