TypeScriptで関数型っぽくプログラミングする

@maxfie1dと申します。Webが得意なフロントエンドエンジニアです。
Qiitaの記事は久しぶりなので少し緊張気味です。

この記事はTypeScript(TS)で関数型っぽくプログラミングしてみようという記事です。なぜ関数型っぽく書くかというと、**明確で分かりやすく簡単なコードになるから**です。いやマジで！

この記事で言う関数型っぽいとはどういうことかというと...

* `Maybe<T>`, `Result<E, T>`
* カリー化
* イミュータブル
* パイプ、合成関数
* パターンマッチ

これらを実現できたら(僕的には)充分です。順番にやってみましょう！

※詳しい用語や背景の説明は飛ばすので、関数型言語を触ったことの無い人にはつらいかもしれません。ですが、サンプルコードを見れば大体の動きは想像できると思います。とにかく、こういう書き方できるんやなーと知ってくれたら嬉しいです！ライブラリに[Folktale](https://github.com/origamitower/folktale)と[Ramda](https://github.com/ramda/ramda)を使用します。

## Maybe\<T>, Result\<E, T>

TSだと一般的に`undefined`や`null`、もしくは例外で「値が存在しないこと」や「失敗」を表しますが、`Maybe<T>`や`Result<E, T>`を使うという方法もあります。

```typescript:Maybeの例
import { maybe, Maybe } from "folktale";

const myMaybe: Maybe<number> = trueOrFalse()
  ? maybe.Just(123)
  : maybe.Nothing();

myMaybe.matchWith({
  Just: ({ value }) => {
    console.log(value); // => 123
  },
  Nothing: () => {
    console.log("値がないよ");
  }
});
```

```typescript:Resultの例
import { result, Result } from "folktale";

const myResult: Result<string, number> = trueOrFalse()
  ? result.Ok(123)
  : result.Error("失敗だよ");

myResult.matchWith({
  Ok: ({ value }) => {
    console.log(value); // => 123
  },
  Error: ({ value }) => {
    console.log(value); // => "失敗だよ"
  }
});
```

`Maybe<T>`と`Result<E, T>`の違いは、異常の場合に値を持つかどうかです。

## イミュータブル

TSだと、配列の要素追加を`array.push(...)`で行いますが、これは要素を追加した新しい配列を返すのではなく既存の配列に値が追加されます。つまりミュータブルです。(ちなみに`Array.push`の返り値の型は`number`で新しい配列の長さが返されます。)

イミュータブルにやってみましょう。

```typescript:イミュータブルな配列操作の例

import * as R from "ramda";

const array = [1, 2, 3];
// ミュータブル
array.push(4);

console.log(array);  // => [1, 2, 3, 4]

// イミュータブル
const array5 = R.append(5, array);
const array0 = R.prepend(0, array);

console.log(array);  // => [1, 2, 3, 4]
console.log(array5); // => [1, 2, 3, 4, 5]
console.log(array0); // => [0, 1, 2, 3, 4]
```

`R.append`も`R.prepend`も元の配列`array`に影響を与えません。つまりイミュータブルです。やったね！

## カリー化

朗報！
カリー化されていない関数を後からカリー化することができます。

```typescript:カリー化の例
import * as R from "ramda";

const add3 = (a: number, b: number, c: number) => a + b + c;
const curriedAdd3 = R.curry(add3);

console.log(add3(1, 2, 3));        // => 6

console.log(curriedAdd3(1)(2, 3)); // => 6
console.log(curriedAdd3(1, 2)(3)); // => 6
console.log(curriedAdd3(1)(2)(3)); // => 6
```


## パイプ、合成関数

僕はF#で関数型言語を学んだのですが、F#には*Pipeline Operator* `|>`というものがあります。パイプライン演算子を使うと、通常*`関数(引数)`*の順で記述するところを逆の*`引数 |> 関数`*の順で記述することができます。

他にもF#には*Composition Operator* `>>`というものがあり、`f >> g`のようにして合成関数を作ることができます。


残念ながらTSでは`|>`や`>>`といった演算子は使用できませんが、似たことを[Ramda](https://github.com/ramda/ramda)を使ってやってみましょう。`pipe`と`compose`の違いは関数の適用順序です。

```typescript:pipeとcomposeの例
import * as R from "ramda";

type Fn = (xs: number[]) => number[];
const f: Fn = R.filter(x => x % 2 === 0);
const g: Fn = R.map(x => x * 2);
const h: Fn = R.filter(x => x > 5);
const numbers = R.range(1, 10);

const r1 = R.pipe(f, g, h)(numbers);
const r2 = R.compose(f, g, h)(numbers);

console.log(r1); // => [8, 12, 16]
console.log(r2); // => [12, 14, 16, 18]
```

関数をパイプしたり合成するメリットの1つは不要な一時変数(しばしば命名にも悩む)を置く必要がなくなることです。

ちなみに、[2019年11月26日現在パイプライン演算子はECMAScriptでstage-1](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Pipeline_operator)なんですね。知らなった。[try-babel](https://babeljs.io/repl)で試せます(`presets`の`stage-1`をオンにしておきましょう)。

将来的にTSでもパイプライン演算子が使えるようになったら嬉しいですね。

(ちなみにちなみに、記事を書いている途中に[Rubyの開発版でパイプライン演算子が導入された](https://bugs.ruby-lang.org/issues/15799)ことを知りました。まじか。)

## パターンマッチ

関数型っぽく書きたいならパターンマッチも欲しいですよね。しかし、残念ながら現状では難しいみたいです。[提案はstage-1](https://github.com/tc39/proposal-pattern-matching)で、将来的には使えるようになるかもしれませんがいつのことになるやら。


どうしてもやってみたい人は[babelの実装](Pattern-matching with babel https://github.com/babel/babel/pull/9318)や[funcy](https://github.com/bramstein/funcy)などを使ってみましょう。

## おしまいに

[Folktale](https://github.com/origamitower/folktale)と[Ramda](https://github.com/ramda/ramda)を使うことで、TSでも結構関数型っぽく書くことができて関数型スタイルの恩恵を受けることができます。

他にもJSのプラットフォームで関数型を使うアイデアとして、例えばF#をJSにコンパイルする[Fable]()、JSコンパイルに対応している[Kotlin](https://kotlinlang.org/)、AltJSの[LiveScript](https://github.com/gkz/LiveScript)や[PureScript](https://github.com/purescript/purescript)や[Reason](https://github.com/facebook/reason)を使うという手があります。

[awesome-fp-js](https://github.com/stoeffel/awesome-fp-js)を覗いてみるともっと面白い発見があるかもしれません！

## リソース
* [この記事のコード例(GitHub)↗️](https://github.com/maxfie1d/qiita-advent-calendar-2019)
* [Folktale(GitHub)↗️](https://github.com/origamitower/folktale)
* [Ramda(GitHub)↗️](https://github.com/ramda/ramda)
* [Kotlin↗️](https://kotlinlang.org/)
* [LiveScript↗️](https://github.com/gkz/LiveScript)
* [PureScript↗️](https://github.com/purescript/purescript)
* [Reason↗️](https://github.com/facebook/reason)
* [awesome-fp-js↗️](https://github.com/stoeffel/awesome-fp-js)