fp-ts不完全ガイド part3 bindとsequenceT

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

https://qiita.com/Yuumillar/items/0de0a4222cb9794dd72b

part1では`Either`、 part2では`Option`について説明した。次はpipe処理をするうえで有用な`bind`について説明する。bindとは、pipeの工程で取得した値を、後の工程で再度利用したいときなどに使われる。

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

// User型
type User = {
	id: number;
	name: string;
	age: number;
	familyId: number;
};

// DBから取得してきたデータとする
const usersTable: User[] = [
	{
		id: 1,
		name: "taro",
		age: 20,
		familyId: 1,
	},
	{
		id: 2,
		name: "jiro",
		age: 30,
		familyId: 1,
	},
	{
		id: 3,
		name: "saburo",
		age: 40,
		familyId: 2,
	},
];

// getUserId関数は、ランダムな値によって、ユーザーIDの取得に失敗したり成功したりする関数
const getUserId = (): E.Either<string, number> => {
	const rnd = Math.random();
	if (rnd > 0.5) {
		return E.right(1);
	}
	return E.left("user_id is not found");
};

// getUserData関数は、ユーザーIDを元にユーザー情報を取得する関数
const getUserData = (id: number): E.Either<string, User> => {
	return pipe(
		usersTable,
		A.findFirst((user) => user.id === id),
		E.fromOption(() => "user is not found"),
	);
};

// getUserFamily関数は、ユーザー情報を元に家族情報を取得する関数
const getUserFamily = (user: User): E.Either<string, User[]> => {
	return pipe(
		usersTable,
		A.filter((u) => u.familyId === user.familyId),
		E.of, // 実際にはDB接続があり、たまにErrorとなるのでE.ofでEither型に変換しておく
	);
};

// getUserIncome関数は、ユーザーIDを元に収入情報を取得する関数
const getUserIncome = (id: number): E.Either<string, number> => {
	const userIncomesTable = [
		{
			id: 1,
			income: 100,
		},
		{
			id: 2,
			income: 200,
		},
		{
			id: 3,
			income: 300,
		},
	];
	return pipe(
		userIncomesTable,
		A.findFirst((user) => user.id === id),
		E.fromOption(() => "user is not found"),
		E.map((user) => user.income),
	);
};

type CompletedUser = {
	id: number;
	name: string;
	age: number;
	familyList: User[];
	income: number;
};

const completedUser: E.Either<string, CompletedUser> = pipe(
	// userIdをEitherにbindする
	getUserId(),
	E.bindTo("userId"),

	// userIdを元にユーザー情報を取得する
	E.bind("userData", ({ userId }) => getUserData(userId)),

	// ユーザー情報を元に家族情報を取得する
	E.bind("userFamily", ({ userData }) => getUserFamily(userData)),

	// userIdを元に収入情報を取得する ここでuserIdを使いたいのでbindToを使える
	E.bind("userIncome", ({ userId }) => getUserIncome(userId)),
	// ここでbindしていたものをすべて使うことができる
	E.map(({ userId, userData, userFamily, userIncome }) => ({
		id: userId,
		name: userData.name,
		age: userData.age,
		familyList: userFamily,
		income: userIncome,
	})),
);
```

関数の中身はダミーなのであまり意味はない。pipe処理のなかを見ていこう。

まず、最初にgetUserId関数がEitherを返している。そのEitherに名前をつけて、あとの工程で使うことができるのだ。今回はbindToでuserIdという名前に束縛している。次にbindしたuserIdを用いて、getUserData関数でユーザー情報を取得。そして取得したデータもuserDataという名前に束縛している。これ以後、たとえばmap関数などのpropsにuserIdもuserDataも入った状態で処理をつづけることができるのだ。getUserFamilyは良いとして、その次の処理で前にbindしているuserIdを再利用している。さらに最後のmap処理でbindした値を全部使うことができている。

bindの中身はflatMapと同じと考えてよい。bindのなかではEither型を返せば良い。もし必ず成功してしまう関数だがEither型の文脈で処理をつづけたい場合はE.ofなどで無理やりEither型にしてしまっても良いだろう。

### sequenceT

bindだけでは短くなるので、ついでにsequenceTについても書こう。

sequenceTは複数あるOptionやEitherをひとつにまとめることができる。どういうことなのか説明しよう。

```ts
import * as O from "fp-ts/Option";
import { sequenceT } from "fp-ts/lib/Apply";
import { pipe } from "fp-ts/lib/function";

// 4つのOption型の値を持つ変数を定義
const Option1 = O.some(1);
const Option2 = O.some(2);
const Option3 = O.some(3);
const Option4 = O.some(4);

// 4つのOption型の値を持つ変数をsequenceT関数で結合。allの型はO.Option<[number, number, number, number]>となる
const all = sequenceT(O.Applicative)(Option1, Option2, Option3, Option4);

if (O.isSome(all)) {
	//OptionがすべてSomeの場合、ここで値を取り出すことができる
	console.log(all.value);
} else {
	//Optionが一つでもNoneの場合、ここでエラー処理を行う
	console.log("error");
}

//当然、fold関数を使ってもOK。今回はerrorのときにstringを返す都合上、foldWにしてみた。

const result = pipe(
	all,
	O.foldW(
		() => "error",
		([num1, num2, num3, num4]) => num1 + num2 + num3 + num4,
	),
);
console.log(result); // 10

複数あるOption型をひとつにまとめて処理できる。もしもTypeScriptで書くなら以下のようになるだろう。
const mayUndefinedNum1: number | undefined = 1;
const mayUndefinedNum2: number | undefined = 2;
const mayUndefinedNum3: number | undefined = 3;
const mayUndefinedNum4: number | undefined = 4;

// 上記がすべてnumberのときだけ処理したい

const mayUndefinedNums = [
	mayUndefinedNum1,
	mayUndefinedNum2,
	mayUndefinedNum3,
	mayUndefinedNum4,
];
const isAllNumber = mayUndefinedNums.every((num) => num !== undefined);
if (isAllNumber) {
	const validNums: number[] = mayUndefinedNums;
}
```

こういう数字の例ではぴんと来ないかもしれないが、たとえばReactで複数のカスタムフックがあるとする。それぞれ異なるデータをtrpcで取得してくるが、それは当然ながら成功するかもしれないし、失敗するかもしれないEitherの値となる。そのときにすべてのデータが揃っているときだけ、DOMを表示したい。そういうときにsequenceTを使ってEither型をひとまとめにして処理ができる。ひとつでもEitherがあればnullを返せば良い。

次回はTE(Task Either)型について説明しよう。