fp-ts不完全ガイド part2 Option

## Option

https://qiita.com/Yuumillar/items/f3241346fc28c345cfba

part1ではEither型について説明した。

  

今回はOption型である。これは前回のEither型とよく似ているので、Eitherが理解できた人には簡単だろう。

  

EitherはLeft（Error）とRight（Success）の値を取った。

OptionはLeftの代わりにNone, Rightの代わりにSomeという値を取る。

Option.noneとOption.some(3)など。isSome, isNoneがisRight, isLeftと対応している。



Optionとは、JavaScriptで言えばundefined, nullを表す型である。あるのかないのかわからない値。そういう値は、実際にプログラミングをしていても良く発生する。もっともよくあるのは、配列にindexでアクセスしたときだろう。

  

```ts
import * as A from "fp-ts/Array";
const numbers: number[] = [];
const res = A.head(numbers);
console.log({ res }); // res: { _tag: 'None' } }

const numbers2: number[] = [1, 2, 3];
const res2 = A.head(numbers2);
console.log({ res2 }); // res2: { _tag: 'Some', value: 1 }
```

Arrayモジュールを利用し、`head`という関数を使用している。headは配列のなかで一番最初にある要素を取得するものである。Eitherのときとは異なり、NoneとSomeという値を取る。Optionは簡単に言ってしまえば、エラーメッセージのないEitherである（逆に、Eitherはエラーメッセージを取れるOptionとも言える）。

  

仮に上記に同じ操作を通常のTypeScriptで書くと以下のようになる。

```ts
const numbers: number[] = [];
const res: number | undefined = numbers[0];
console.log({ res }); // res: undefined

const numbers2: number[] = [1, 2, 3];
const res2: number | undefined = numbers2[0];
console.log({ res2 }); // res2: 1
```

わかりやすいように型注釈をつけているが、resはnumberとundefinedのユニオン型になる。ちなみに、tsconfig.jsonにて"noUncheckedIndexedAccess”をtrueにしておかないと、ただのnumber型になってしまいミスを起こしやすいので注意。
  

Eitherのときと同じように、Option型の値に数字を足してみよう。
 

```ts
import * as O from "fp-ts/Option";
const optionNum = O.some(20);

const optionValue = O.getOrElse(() => 1)(optionNum);
const add100 = optionValue + 100;
console.log(add100); // 120

const optionNumNone = O.none;
const optionValueNone = O.getOrElse(() => 1)(optionNumNone);
const add100None = optionValueNone + 100;
console.log(add100None); // 101
```

  

O.someは値が存在するとき、O.noneは値が存在しないときのOption型の値を作成することができる。上記例のように、O.some(20)と普通に数字を入れるようなことは、あんまりしないと思われる。自分で定数として数字を入れられるならば、それは絶対にnullableな値ではなく、numberだとわかっている。わざわざOptionにしなくても良い。さきほどのように、fp-tsのライブラリを用いて配列から値を取得したときにOption型の値になっていることが多い。Eitherのときと同様にif文で処理することもできる。その場合はisNoneで判定して早期リターンするとよいだろう。

  

pipeでの処理は以下のようになる。
  

```ts
import * as A from "fp-ts/Array";
import * as O from "fp-ts/Option";
import { pipe } from "fp-ts/function";

const res = pipe(
	[1, 2, 3, 4, 5],
	A.lookup(3), // Some(4)
	O.map((x) => x * 100), // Some(400)
	O.map((x) => x / 2), // Some(200)
);
console.log(res); // Some(200)

```

今回は`head`ではなく`lookup`という関数を使用した。これはindexでアクセスするのと同様である。当然ながらnullableな値が返ってくる可能性があるので、返り値はoption型の値になる。
 

pipeとmapを使うことで一時的な変数名を宣言せずとも処理をつづけることができる。

仮に手続き型で書くと、以下のようになるだろうか。

  
```ts
const process = () => {
	const numbers = [1, 2, 3, 4, 5];
	const num = numbers[3];
	if (!num) return;
	const result = (num * 100) / 2;
	console.log(result);
};
```

書いてみて思ったけれど、わざわざ2回mapをせずとも、\* 100と/ 2くらいなら一緒に処理をしてしまったほうが良い。


### Array, Eitherと組み合わせるReactの例

もう少し実践的な例を見てみよう。次はReactで、小説を選択できる。選択した場合は小説の本文が表示される。こういうリストからひとつを選ぶデザインは、WEBアプリケーションでは頻出だ。

  

```tsx
import * as A from "fp-ts/Array";
import * as E from "fp-ts/Either";
import * as O from "fp-ts/Option";
import { pipe } from "fp-ts/lib/function";
import { useState } from "react";

// 前回Either型を使って、parseInt関数を安全に使う方法を紹介したときの関数。
export const parseIntEither = (input: string): E.Either<string, number> => {
	const result = Number.parseInt(input, 10);
	// isNaNのときは、E.left("Invalid input")を返す。それ以外は、E.right(result)を返す。
	return Number.isNaN(result) ? E.left("Invalid input") : E.right(result);
};

type Article = {
	id: number;
	title: string;
	body: string;
};

const ArticleSelect = () => {
	const articleList: Article[] = [
		{ id: 1, title: "すべてがFになる", body: "今は夏。彼女はそれを思い出す。" },
		{
			id: 2,
			title: "スプートニクの恋人",
			body: "22歳の春にすみれは生まれて初めて恋に落ちた。",
		},
		{
			id: 3,
			title: "傀儡のマトリョーシカ", // 拙作
			body: "「友達になってくれないか？」俺は少女に声をかけた。",
		},
	];

	const [selectedArticle, setSelectedArticle] = useState<O.Option<Article>>(
		O.none,
	); // Option<Article>型のstateを作成する

	return (
		<div>
			<select
				onChange={(e) =>
					pipe(
						e.target.value, // e.target.valueはstring型
						parseIntEither, // idがstringで来るのでEitherのnumber型に変換する
						O.fromEither, // EitherをOptionに変換する
						O.flatMap((id) =>
							pipe(
								articleList,
								A.findFirst((article) => article.id === id),
							),
						), // idに対応するArticleを取得する
						setSelectedArticle, // Option<Article>をセットする。
					)
				}
			>
				{articleList.map((article) => (
					<option key={article.id} value={article.id}>
						{article.title}
					</option>
				))}
			</select>
			<div>
				{
					// selectedArticleがSomeのとき、bodyを表示する。
					pipe(
						selectedArticle,
						O.fold(
							() => null,
							(article) => article.body,
						),
					)
				}
			</div>
		</div>
	);
};
```

  

onChangeの部分は、`e.target.value`を`parseInt`にしてArticleListからidで検索をする、という流れをpipeでつなげている。`O.fromEither`でEither型の結果からerrorの情報をなくしてOption型に変換している。型安全になっており、エラーが起きづらい処理になっている。Either型とOption型、Array型など様々な型をpipeで共通して一連の流れとして処理ができる。if文も不要だ（最後にfoldを使うところだけが条件分岐と言える）。少しやり過ぎ感もある。ここまでやらんでも、と思わなくもない。しかし、将来的にEither型の返り値を持つ関数やカスタムフックを多用するようになると、徐々にこのような書き方になっていくのだ。

  

また、selectedArticleのbodyを表示する部分にfoldを使用している。ここは普通のJavaScriptなら selectedArticle && {selectedArticle.body} と短絡評価で処理をする場面だろう。そっちのほうが早いときもある。仮にOption型の短絡評価で書くなら以下のようになる。

```tsx
<div>{O.isSome(selectedArticle) && selectedArticle.value.body}</div>
```

  

  

### fromNullable

  

Option型の値はfromNullableという形からつくられることも多い。たとえば、自分以外のつくったライブラリで、undefinedとnullとnumberが返ってくる関数があったとしよう。そういう値をOption型で包んだあとに処理をすることができる。if文で先に処理をするか、pipeとmapで処理をして、最後にnullableなケースを処理するか。個人的には例外処理を忘れにくいpipe処理のほうが好みだ。

```ts
import * as O from "fp-ts/Option";
import { pipe } from "fp-ts/function";
const getNullableNumber = (): number | undefined | null => {
	const rnd = Math.random();
	if (rnd < 0.3) {
		return undefined;
	}
	if (rnd < 0.6) {
		return null;
	}
	return rnd;
};
const res = pipe(
	getNullableNumber(),
	O.fromNullable,
	O.map((n) => n.toFixed(2)),
); // => Option<string> "0.80"……など

console.log(res);
```

  

OptionにもEitherと同じようにtryCatchがある。というか、Eitherにあった大半の型がOptionにもある。mapもmapLeftもflatMapもある。isLeftはないが、isNoneがある。fp-tsは、ひとつ覚えたら他の型でも似たような処理があることが多い。ここで学んだ概念はfsharpやscalaでも有用だろう。


### Option型を使用する理由

undefinedやnullと言った型があるのに、なぜOption型を使用したほうが良いのか。前述したようにpipeで処理できるのが大きい。あとは、undefined, nullとは異なり、値があるかもしれないしないかもしれない、という状態を明確に表現している。Eitherの文脈に変換することもできるし、逆にEitherからOptionにすることもできる。あとはfp-tsのArrayなどでOptionが返ってくることもある。fp-tsで処理を書きたい場合は、やはりOptionに寄せていくことになる。APIのレスポンスやparseなどはエラーの情報が必要なのでEither型のほうが良いだろう（本当はTE型になるが、それはまた別の機会に）。

  

### EitherとOptionの使い分け
  

EitherとOptionはよく似ている。同じような処理が書ける。一番の違いは、Eitherにはerrorの理由が書かれることである。対してOption型は、値があるかないかという状態を表すが、なぜか、という理由はない。単に配列の先頭にある値を取り出したいときに、原因までは知らなくても良い。単に配列が空なのだろう。失敗するときの理由を知りたいときはEither、値が存在するかもしれないし存在しないかもしれない。そういう状態を表したいときはOptionを使うことになる。

  
Eitherと重複する箇所も多いので短めになった。次はBindについて書くことにしよう。