fp-ts ユーザが Scala with Cats を読み終えたので、fp-ts と Cats の違いをまとめてみた
Tags: scala, typescript, functional, cats
Published: 2022-02-28 03:26:19

## tl; dr

Scala の fp ライブラリ Cats と、TypeScript の fp ライブラリ fp-ts を比較してみました。主に言語仕様の違い（評価戦略の違いや型コンストラクタの有無など）によって、高カインド型や型クラスの実装方法に違いがあることや、Cats でのみ提供されている型クラスがあること等がわかりました。

## この記事を書くことにした経緯

この記事を書くことにしたきっかけは、勤務先の 株式会社 HERP で、Cats の教材 ["Scala with Cats"](https://www.scalawithcats.com/dist/scala-with-cats.html)の輪読会を開催したことです[^1]。弊社では Cats ではなく fp-ts を Web フロントエンド・サーバサイドで全面的に使用しているのですが[^2]、 fp-ts は比較的まだ若いライブラリであり[^3]、Learning Resource もさほど充実していないという課題意識がありました。そこで、「fp-ts は Cats から影響を受けている[^4]し、Cats の Learning Resource に良いものがあればそれを勉強してもよいのでは？」と考えた次第です。

輪読会を終えて、「普段 fp-ts を使っている人から見た Cats あるいは Scala」という観点で記事を書いたら面白いのではないか、と考えこの記事を書き始めるに至りました。

## 対象読者

まず前提として、この記事は「明日から使えるプログラミングテクニック」について書かれたものではありません。この記事はいわば「読み物」ですので、暇なときに読んでいただき「へ〜」と思っていただけたら良いなと思って書いております。

そしてこの「読み物」の対象読者ですが、以下のうち、一つ以上に当てはまる方を想定しております：

- fp-ts を使ったことがある
- Cats を使ったことがある
- （プログラミング言語を問わず）Functional Programming を実践している

## Scala with Cats

Scala with Cats は Web 上で無料で公開されており、随時内容が更新されています。英語のリソースですが、全体的に平易な英語で書かれている印象でした。この記事の執筆時点（2021 年 12 月）では、全七章[^5]で以下の内容が扱われています。

- The Type Class
- Monoids
- Semigroups
- Functors
- Monads
- Monad Transformers
- Semigroup and Applicative
- Foldable and Traverse

そもそも私は Scala を書いたことがほぼなかったのですが、Scala with Cats では逐次 Scala の言語仕様そのものについても解説されているため、無理なく読み進めることができました。

私が Scala with Cats を読んで良かったことは、まず 「fp-ts にもあることは知っているけれど、よく理解していなかった型クラス」について知ることができたことです。上に挙げたとおり、Scal with Cats では主要な型クラスについての解説が一通り用意されています。

また、それらの型クラス同士の関係性（e.g. Functor と Applicative と Monad の関係性、Semigroup と Applicative の関係性）についても説明されており助かりました。偏見ですが、「Functor と Applicative と Monad の関係性」なんかは、圏論の本を読んだりしないとなかなか知る機会がないのではないかと思っています。

さらに、普段筆者は業務で fp-ts を使っているため、「fp-ts ではこういう面倒くさいことをしなければならないが、Cats (Scala) では同じようなことがより簡潔に書けるのか」といった気づきがありました。以降のセクションには、そうした「気付き」をそれぞれできるだけ具体的にまとめました。

## fp-ts と Cats の比較

本題の比較に入ります。なお、fp-ts (TypeScript) および Cats (Scala) のヴァージョンは以下を想定しています。

- fp-ts: `2.11.8` (TypeScript: `4.5.5`)
  - 執筆時点での最新のヴァージョンを想定しています
- Cats: `2.1.0` (Scala: `2.13.1`)
  - 執筆時点での Scala with Cats の最新版に対応したヴァージョンを想定しています


### 高カインド型と型コンストラクタ

Cats では、Functor や Monad の定義において、[Type Constructor （型コンストラクタ）](https://www.scala-lang.org/files/archive/spec/2.11/03-types.html#type-constructors)という言語機能が用いられています。以下は Cats における Functor の定義です（一部省略しています）：

```scala
package cats

import simulacrum.{noop, typeclass}

/**
 * Functor.
 *
 * The name is short for "covariant functor".
 *
 * Must obey the laws defined in cats.laws.FunctorLaws.
 */
@typeclass trait Functor[F[_]] extends Invariant[F] { self =>
  def map[A, B](fa: F[A])(f: A => B): F[B]
  ...
}
```

`trait Functor[F[_]]` と書くことで、ブロックの内側で `F[A]` や `F[B]` という型を使うことができています。ここでは、Type Constructor `F` はカインドが `* -> *` の型（例えば、`Option[A]` や `List[A]` ）をエミュレートしています。このおかげで、`Functor` の `Option[A]` 型や `List[A]` 型のインスタンス（`FunctorForOption[A]`、`FunctorForList[A]`） を個別に定義する必要がなくなっています。

#### TypeScript における高カインド型の実装

一方、TypeScript には Type Constructor （相当の機能）がありません。ではどのように `Functor` などの型クラスを実装しているのでしょうか[^6]。

超シンプルな型： `type Identity<A> = A` について、 fp-ts の `Functor` のインスタンスを導出する方法を、例として見ていきます。

```ts
// Identity.ts

import { Functor1 } from "fp-ts/lib/Functor";

export const URI = "Identity";

export type URI = typeof URI;

declare module "fp-ts/lib/HKT" {
  interface URItoKind<A> {
    readonly Identity: Identity<A>;
  }
}

export type Identity<A> = A;

// Functor instance
export const identity: Functor1<URI> = {
  URI,
  map: (ma, f) => f(ma),
};
```

ここで、 fp-ts の `Functor1` の定義は以下の通りです：

```ts
// fp-ts/lib/Functor.ts

export interface Functor1<F extends URIS> {
  readonly URI: F;
  readonly map: <A, B>(fa: Kind<F, A>, f: (a: A) => B) => Kind<F, B>;
}
```

では、 `URItoKind`、 `URIS`、 `Kind` はそれぞれ何でしょうか？

`URItoKind` は type-level map です。型 `URI` をある特定の型に map します。TypeScript の [module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) を利用して、それぞれの型を `URItoKind` に登録しています。

具体的には、`HKT.ts` に `URItoKind` が定義されていて：

```ts
// fp-ts/lib/HKT.ts

export interface URItoKind<A> {}
```

module augmentation を用いて、 `Identity.ts` に、以下のように「`Identity` を `URItoKind` に登録する」コードを書くことができます：

```ts
// Identity.ts

declare module "fp-ts/lib/HKT" {
  interface URItoKind<A> {
    readonly Identity: Identity<A>; // maps the key "Identity" to the type `Identity`
  }
}
```

`URIS` は `keyof URItoKind<any>` であり、 `Functor1` interface において `URItoKind` に登録されていない HKT が Functor1 の instance だということにされないよう、型レベルの制約として機能しています（`interface Functor1<F extends URIS>`）。

`Kind<F, A>` は `Kind<URI extends URIS, A> = URI extends URIS ? URItoKind<A>[URI] : any` です。例えば `URI = 'Identity'` のとき、 `Kind<URI, number>` は `Identity<number>` へと写されます。`URI` が `URI extends URIS` を満たさない、例えば `HOGEHOGE` という値だった場合には、 `any` 型となります。

ここで、 `Identity` は kind が `* -> *` (`Option` や `List` と同じ) でしたので `Kind<F, A>` というシグネチャになっていましたが、 kind が `* -> * -> *` であるような型はどのように表現されるのでしょうか。

そのような型のために `URItoKind2`, `URIS2`, `Kind2` が定義されています。 `Either` を例として、その使われ方を見てみます：

```ts
// Either.ts

import { Functor2 } from "fp-ts/lib/Functor";

export const URI = "Either";

export type URI = typeof URI;

declare module "fp-ts/lib/HKT" {
  interface URItoKind2<E, A> {
    readonly Either: Either<E, A>;
  }
}

export interface Left<E> {
  readonly _tag: "Left";
  readonly left: E;
}

export interface Right<A> {
  readonly _tag: "Right";
  readonly right: A;
}

export type Either<E, A> = Left<E> | Right<A>;

// Functor instance
export const either: Functor2<URI> = {
  URI,
  map: (ma, f) => (ma._tag === "Left" ? ma : right(f(ma.right))),
};
```

ここで、 `Functor2` の定義は以下の通りです：

```ts
// fp-ts/lib/Functor.ts

export interface Functor2<F extends URIS2> {
  readonly URI: F;
  readonly map: <E, A, B>(fa: Kind2<F, E, A>, f: (a: A) => B) => Kind2<F, E, B>;
}
```

では、`Functor` のような HKT を扱う関数、すなわち Cats では Type Constructor を使って定義されるような関数を、fp-ts を使う場合ではどう定義するのでしょうか？ 以下の `lift` 関数を例に見ていきます：

```ts
import { HKT } from "fp-ts/lib/HKT";

export function lift<F>(
  F: Functor<F>
): <A, B>(f: (a: A) => B) => (fa: HKT<F, A>) => HKT<F, B> {
  return (f) => (fa) => F.map(fa, f);
}
```

実はここまでに触れられてこなかった `HKT` という型がようやく登場しました。定義は以下の通りです。

```ts
// fp-ts/lib/HKT.ts

export interface HKT<URI, A> {
  readonly _URI: URI;
  readonly _A: A;
}
```

`HKT` 型は、kind が `* -> *` であるような型のための Type Constructor を表現しています。もうお気づきの方もいらっしゃるかもしれませんが、`Functor<n>` や `URITtKind<n>` と同様に、 `HKT<n>` が `fp-ts/lib/HKT.ts` に定義されています。ただし、fp-ts では `HKT4` （kind が `* -> * -> * -> * -> *` の型）にまで対応しています。

さて、 `HKT` 型を定義したので、これでようやく `lift` のような関数の型定義を書くことができる…と言いたいところですが、実はもうひと工夫必要です。

```ts
const double = (n: number): number => n * 2;

//                             v-- the Functor instance of Identity
const doubleIdentity = lift(identity)(double);
```

以上のコードは、以下のように compile error となります：

```
Argument of type 'Functor1<"Identity">' is not assignable to parameter of type 'Functor<"Identity">'
```

`Functor1<"Identity">` と `Functor<"Identity">` はあくまで別の型なので、`lift` の型定義を書く上でオーバーロードを定義する必要があります：

```ts
export function lift<F extends URIS2>(
  F: Functor2<F>
): <A, B>(f: (a: A) => B) => <E>(fa: Kind2<F, E, A>) => Kind2<F, E, B>;
export function lift<F extends URIS>(
  F: Functor1<F>
): <A, B>(f: (a: A) => B) => (fa: Kind<F, A>) => Kind<F, B>;
export function lift<F>(
  F: Functor<F>
): <A, B>(f: (a: A) => B) => (fa: HKT<F, A>) => HKT<F, B>;
export function lift<F>(
  F: Functor<F>
): <A, B>(f: (a: A) => B) => (fa: HKT<F, A>) => HKT<F, B> {
  return (f) => (fa) => F.map(fa, f);
}
```

`lift` が `Functor`, `Functor1`, `Functor2` に対応できるようオーバーロードを定義したので、以下のように `lift` は `Identity` のインスタンスも `Either` のインスタンスも引数に取ることができます：

```ts
// `doubleIdentity` has type `(fa: Identity<number>) => Identity<number>`
const doubleIdentity = lift(identity)(double);

// `doubleEither` has type `<E>(fa: Either<E, number>) => Either<E, number>`
const doubleEither = lift(either)(double);
```

だいぶ長くなりましたが以上です。HKT を実装することができありがたいのですが、この手法には二点弱点があります。

##### fp-ts の HKT 実装の弱点その 1 「型レベルで部分適用できない」

１つめの弱点としては、「`HKT2<"URI", A, B>` を「型レベルで部分適用」して、`HKT1` をつくる」ということができません。

「型レベルで部分適用」というのは、例えば `Either` 型は kind が `* -> * -> *` ですが、proper な型（例えば、`String`） を渡すことで、kind が `* -> *` の型となります。Haskell の REPL には`:k`というコマンドがあり[^7]、型の kind を調べることができます。以下は `Either` を「型レベルで部分適用」していく例です。

```haskell
Prelude> :k Either
Either :: * -> * -> *
Prelude> :k Either String
Either String :: * -> *
Prelude> :k Either String Int
Either String Int :: *
```

他方、TypeScript では「型レベルで部分適用」ができません。

kind が `* -> * -> *` の型 `HKT2<"either", A, B>` について、`A = string` として `type EitherAsHKT1<B> = HKT2<"either", string, B>` という型を定義したとします。

`EitherAsHKT1<B>` は型パラメータを 1 つだけ取るため、一見 kind が `* -> *` のようですが、実際のところ `HKT2<"either", string, B>` のエイリアスですので、kind は `* -> * -> *` という扱いになってしまうのです。

ちなみに、Scala の REPL にも `:K` が実装されており[^8]、使ってみると以下のようになります：

```scala
scala> :k Int
Int's kind is A

scala> :k Either
Either's kind is F[+A1,+A2]

scala> :k Option
Option's kind is F[+A]

scala> type IntOrA[A] = Either[Int, A]
defined type alias IntOrA

scala> :k IntOrA
IntOrA's kind is F[A]
```

kind は Type Constructor の `F` を用いて表現されており、`* -> * -> *` は `F[+A1, +A2]` で、`* -> *` は `F[+A]` で、そして `*` は `A` で表現されています。そして問題の「型レベルで部分適用」ですが、Scala では `type IntOrA[A] = Either[Int, A]` として定義した型の kind が `F[A]` となっていることがわかります。

##### fp-ts の HKT 実装の弱点その 2 「HKT を使う上で必要になるボイラープレートコードが多すぎ」

2 つ目の弱点は、HKT を使ったコードを書くために必要なオーバーロードが多すぎるという点です[^9]。すでに見てきたように、例えば `lift` 関数は次のようにたくさんのオーバーロードを定義する必要がありました。

```typescript
// 再掲
export function lift<F extends URIS2>(
  F: Functor2<F>
): <A, B>(f: (a: A) => B) => <E>(fa: Kind2<F, E, A>) => Kind2<F, E, B>;
export function lift<F extends URIS>(
  F: Functor1<F>
): <A, B>(f: (a: A) => B) => (fa: Kind<F, A>) => Kind<F, B>;
export function lift<F>(
  F: Functor<F>
): <A, B>(f: (a: A) => B) => (fa: HKT<F, A>) => HKT<F, B>;
export function lift<F>(
  F: Functor<F>
): <A, B>(f: (a: A) => B) => (fa: HKT<F, A>) => HKT<F, B> {
  return (f) => (fa) => F.map(fa, f);
}
```

これは大変なことですが、fp-ts とは別の [Effect-TS](https://github.com/Effect-TS/core) というライブラリでは、この問題を考慮した HKT の実装が採用されているようです。具体的には、`* -> *`, `* -> * -> *` などの複数の kind について、fp-ts の実装のように `Kind1`, `Kind2`, ... と個別に型を定義するのではなく、共通した 1 つの型を定義しています。例えば、kind を `* -> *` から `* -> * -> * -> *` までに限定して対応するとしたら、`type Kind<F extends URIS, S, R, E> = URItoKind<S, R, E>[F]`　です。もう少し詳しく見ていきます：

```typescript
export interface URItoKind<S, R, E> {
  Effect: Effect<R, E, A>; // * -> * -> * -> *　な型の一例
  Either: Either<E, A>; // * -> * -> *　な型の一例
  Option: Option<A>; // * -> * な型の一例
}

export type URIS = keyof URItoKind<any, any, any, any>;

export type Kind<F extends URIS, S, R, E, A> = URItoKind<S, R, E, A>[F];

export interface Functor<F extends URIS> {
  URI: F;
  map: <A, A2>(
    f: (a: A) => A2
  ) => <S, R, E>(fa: Kind<F, S, R, E, A>) => Kind<F, S, R, E, A2>;
}

export function lift<F extends URIS>(
  F: Functor<F>
): <S, R, E, A, B>(
  f: (a: A) => B
) => (fa: Kind<F, S, R, E, A>) => Kind<F, S, R, E, A> {
  return (f) => (fa) => F.map(f)(fa);
}
```

こちらの `lift` の定義では、`Kind1`, `Kind2` などのために個別にオーバーロードを定義する必要がなくなっています。一方で、個人的には初見で `URItoKind` や `Kind` の定義を見たときの認知的負荷は、扱う型パラメータの数が増えた分こちらのほうが比較的きつい印象があります。ただし、fp-ts での実装を理解してからこちらの実装を見ていく流れであればほとんど気にならないかなとも思います。

### implicit キーワード

Scala の個人的にすごいと思う言語機能として、[`implicit` キーワード](https://docs.scala-lang.org/tour/implicit-parameters.html#:~:text=A%20method%20can%20have%20an,it%20can%2C%20pass%20it%20automatically.)があります。Cats のような型クラスを提供する類のライブラリにとっては、`implicit` キーワードのおかげで、「ある型 `A` とある型クラス `T` について、`A` のための `T` のインスタンスを（実装されていれば）コンパイラが勝手に見つけてきてくれる」という嬉しさがあります。

Cats が提供するすべて型クラスは `apply[A]` というメソッドを提供していて、これは `A` のための `T` のインスタンスを返してくれるスマートコンストラクタです。例えば Cats の提供する `Eq` 型クラスについて、自分が定義した `Cat` 型や `Dog` 型のインスタンスを手に入れるためには次のようにします：

```scala
// EqInstances.scala
object EqInstances {
  // `implicit val` というキーワードで `Eq` のインスタンスを定義する
  implicit val catEq: Eq[Cat] =
    Eq[Cat] { (cat1, cat2) =>
      cat1.name === cat2.name && cat1.age === cat2.age && cat1.color === cat2.color
    }

  implicit val dogEq: Eq[Dog] =
    Eq[Dog] { (dog1, dog2) =>
      dog1.cry === dog2.cry
    }
}

// Main.scala
import EqInstances.EqInstances._
import cats.Eq

// コンパイラは、apply[A] の A から、自動的に対応する instance を見つけてくれる
val eqCat = Eq.apply[Cat]
eqCat.eqv(cat1, cat2)
val eqDog = Eq.apply[Dog]
eqDog.eqv(dog1, dog2)

// `eqv` を使いたいだけであれば、インスタンスを作らなくとも operator として`===` が提供されている
// === の実態は `eqv` なので（後述）、やはり自動的に対応する `Eq` の instance を見つけてくれる
// `cat1 === cat2` と書くと `catEq.eqv` が使われるし、`dog1 === dog2` と書くと `dogEq.eqv` が使われる
cat1 === cat2
dog1 === dog2
```

TypeScript には `implicit val` 相当の言語機能は存在しないため、 fp-ts に `Eq.apply[T]` 相当のスマートコンストラタはありません。fp-ts で `Eq` のインスタンスを実装し、使うには以下のように書きます：

```typescript
// eq-instances.ts
export const eqCat: Eq<Cat> = {
  // fp-ts では `eqv` ではなく `equals` という名前のメソッドを定義する
  equals: (cat1, cat2) =>
    cat1.name === cat2.name &&
    cat1.age === cat2.age &&
    cat1.color === cat2.color,
};

export const eqDog: Eq<Dog> = {
  equals: (dog1, dog2) => dog1.cry === dog2.cry,
};
```

```typescript
// main.ts
import { eqCat, eqDog } from "eq-instances";

// Eq.apply[Cat] みたいなことはできないので、`eqCat`, `eqDog` をそれぞれ直接 import している
eqCat.equals(cat1, cat2);
eqDog.equals(dog1, dog2);
```

また、`implicit` キーワードはメソッド定義の際にも使うことができ、「`A` のための `T` のインスタンスを使うメソッド」を簡潔に定義することができます。以下のコードは Scala with Cats からの引用です：

```scala
package Json
// Define a very simple JSON AST
// sealed: 同一ファイル内からのみ継承可能
sealed trait Json
final case class JsObject(get: Map[String, Json]) extends Json
final case class JsString(get: String) extends Json
final case class JsNumber(get: Double) extends Json
final case object JsNull extends Json

// オレオレ型クラス
// The "serialize to JSON" behaviour is encoded in this trait
trait JsonWriter[A] {
  def write(value: A): Json
}

// オレオレ型クラス `JsonWriter` のインスタンスを定義する場所
object JsonWriterInstances {
  // `implicit val` を用いて、`A = String`, `A = Person`, ...のインスタンスを実装している
  implicit val stringWriter: JsonWriter[String] =
    new JsonWriter[String] {
      def write(value: String): Json =
        JsString(value)
    }

  implicit val personWriter: JsonWriter[Person] =
    new JsonWriter[Person] {
      def write(value: Person): Json =
        JsObject(
          Map(
            "name" -> JsString(value.name),
            "email" -> JsString(value.email)
          )
        )
    }

  // `implicit def` を用いることで、`optionWriter` の実装において `JsonWriter[A]` を使うことができる
  implicit def optionWriter[A](implicit
      writer: JsonWriter[A]
  ): JsonWriter[Option[A]] =
    new JsonWriter[Option[A]] {
      def write(option: Option[A]): Json =
        option match {
          case Some(aValue) => writer.write(aValue)
          case None         => JsNull
        }
    }
  // etc...
}

object Json {
  // **implicit parameter** は、ここでいう `w`。
  // 型クラスを扱うメソッドをこれだけで定義できる（`JsonWriter` の `A` の実装はコンパイラが勝手に見つけてくれる）
  def toJson[A](value: A)(implicit w: JsonWriter[A]): Json =
    w.write(value)
}

final case class Person(name: String, email: String)

object Main extends App {
  import JsonWriterInstances._
  // 1. Json.toJson は implicit parameter として `w: JsonWriter[A]` をとる
  // 2. `JsonWriter[A = Person]` の実装を、`implicit val` で `JsonWriterInstances.personWriter` に定義済みのため、コンパイラはそれを見つける
  println(Json.toJson(Person("Dave", "dave@example.com")))
  println(Json.toJson("hello!"))
  println(Json.toJson(Option("Hoge")))
}
```

Scala with Cats では、この `implicit` キーワードについて、

> Working with type classes in Scala means working with implicit values and implicit parameters.

とさえ書かれています。

直感的に、`implicit` キーワードを用いて、型クラス `T` の、型 `A` のインスタンスを、コンパイラに見つけさせようとするとコンパイルスピードの低下を招きそうですが、コンパイラはソースコードを何も考えずに端から端まで見て回るのではなく、実際には import 済みの `object` 内などの場所を優先的に見て回るため、上記のコード例のようにそういった場所で `A` のインスタンスを定義していればコンパイルスピードに大きな影響はないのかもしれません。


#### `implicit` と 拡張メソッド

Cats における `implicit` の活用例として、拡張メソッドがあります。例えば `cats.syntax.either` では、任意の型 `A` の値から `Either[A, B]` のインスタンスをつくるための Smart Constructor `asLeft[B]` を拡張メソッドとして提供しています。

```scala
import cats.syntax.either._

"Error".asLeft[Int].orElse(2.asRight[String])
// res12: Either[String, Int] = Right(2)
```

上記のコードでは、`String` が本来実装していないはずのメソッド `asLeft` を呼び出しており、また `Int` が本来実装していないはずのメソッド `asRight` を呼び出しています。

Cats はどのようにしてこれを実現しているか、[`cats/core/src/main/scala/cats/syntax/either.scala`](https://github.com/typelevel/cats/blob/v2.1.0/core/src/main/scala/cats/syntax/either.scala) のコードを読んで調べてみます（一部を簡略化しました）：

```scala
// cats/core/src/main/scala/cats/syntax/either.scala
trait EitherSyntax {
  // 2. implicit def を使うことで、`a: A` を `a: EitherIdOps` にキャストしている
  // これをもって `a` から `asLeft` などの `EitherIdOps` のメソッドが呼び出すことができる
  implicit final def catsSyntaxEitherId[A](a: A): EitherIdOps[A] = new EitherIdOps(a)
}

// 1. `AnyVal` を継承したクラス `EitherIdOps` を定義する
// `EitherIdOps` はコンストラクタの引数に型 `A` の値 `obj` を取る
final class EitherIdOps[A](private val obj: A) extends AnyVal {
  /**
   * Wrap a value in `Left`.
   */
  def asLeft[B]: Either[A, B] = Left(obj)

  /**
   * Wrap a value in `Right`.
   */
  def asRight[B]: Either[B, A] = Right(obj)

  // and more!
}
```

[`cats/core/src/main/scala/cats/syntax/either.scala`](https://github.com/typelevel/cats/blob/v2.1.0/core/src/main/scala/cats/syntax/either.scala) では、まず `AnyVal` を継承したクラス `EitherIdOps` を定義しています。`EitherIdOps` はコンストラクタの引数に型 `A` の値 `obj` を取りますが、この `obj` に `asLeft` 等のメソッドを生やしていこうという魂胆です。

次に、`EitherSyntax` という trait を定義し、ここで `implicit final def catsSyntaxEitherId[A](a: A): EitherIdOps[A] = new EitherIdOps(a)` を定義しています。これは任意の型 `A` の値 `a` を `EitherIdOps` 型にキャストしており、したがって `cats.syntax.either` を import した場合には、そこと同じ/そこより内側のスコープにおいて、任意の型 `A` の値には `asLeft` 等の `EitherIdOps` に定義したメソッドが生えることになります。

個人的には、別に `A` のメソッドとして定義しなくても `right(a)` や `left(a)` を使えばよいのでは…？と思ってしまいますが、先の例： `"Error".asLeft[Int].orElse(2.asRight[String])` のようなコードを（pipe 演算子なしで）書きたい場合には、括弧のネストが深くならなくて済むという点では良いのかもしれません。もし他のメリットをご存じの方がいらっしゃいましたら教えてください。

また、Scala においてはオペレータはメソッドなので、`cats/syntax/XXX.scala` ではメソッドと同様にオペレータも提供されています。以下は `Eq` 型クラスの例で、メソッド `eqv` を、オペレータ `===` として提供しています：

```scala
// cats/syntax/eq.scala
package cats
package syntax

trait EqSyntax {
  implicit def catsSyntaxEq[A: Eq](a: A): EqOps[A] = new EqOps[A](a)
}

final class EqOps[A: Eq](lhs: A) {
  def ===(rhs: A): Boolean = Eq[A].eqv(lhs, rhs)
  def =!=(rhs: A): Boolean = Eq[A].neqv(lhs, rhs)
  def eqv(rhs: A): Boolean = Eq[A].eqv(lhs, rhs)
  def neqv(rhs: A): Boolean = Eq[A].neqv(lhs, rhs)
}
```

```scala
// 使うとき
import cats.syntax.eq._ // for === and =!=
123 === 123
123 =!= 234
// type mismatch;
// 123 === "123"
```

この「`eqv` メソッドを中置演算子 `===` としても定義し、使うことができる」という Scala の言語仕様は TypeScript にはなく、結構便利そうな印象です。他には `cats.syntax.semigroup` の `|+|` などがあります。

### 評価戦略

Cats と fp-ts の違いというよりは、Scala と JavaScript の言語レベルでの違いの一つとして、評価戦略があります。

#### Scala の評価戦略と Cats の `Eval`

Scala には、変数・メソッドを定義するためのキーワードとして `val`, `def`, `lazy val` があり、それぞれ以下のようにして使われます：

```scala
val x = {
   println("Computing X")
   math.random
 }
 // Computing X
 // x: Double = 0.15241729989551633

 x // first access
 // res0: Double = 0.15241729989551633
 x // second access
 // res1: Double = 0.15241729989551633 <- 一度目の access での評価の結果が記憶されている（Memoization）

 def y = {
   println("Computing Y")
   math.random
 }

 y // first access
 // Computing Y
 // res2: Double = 0.6963618800921411
 y // second access
 // Computing Y
 // res3: Double = 0.7321640587866993 <- 一度目の access での評価の結果は記憶されていない。

 lazy val z = {
   println("Computing Z")
   math.random
 }

 z // first access
 // Computing Z
 // res4: Double = 0.18457255119783122
 z // second access
 // res5: Double = 0.18457255119783122 <- 一度目の access での評価の結果が記憶されている
```

このコードからもわかる通り、`val`, `def`, `lazy val` は、

1. 右辺がいつ評価されるか
2. 評価結果はメモ化されるかどうか

の二点において異なります。この違いを以下の表に改めてまとめました。

|                          | `val`                                                    | `def`               | `lazy val`             |
| ------------------------ | -------------------------------------------------------- | ------------------- | ---------------------- |
| キーワードの役割         | creates an immutable variable (like `final` in Java) | define a method | 文字通り lazy な `val` |
| 評価のタイミング         | 即時                                                     | 遅延                | 遅延                   |
| 評価結果はメモ化される？ | yes                                                      | no                  | yes                    |

Cats では、これらの評価戦略を抽象化する目的で、[`Eval`](http://typelevel.org/cats/api/cats/Eval.html) という型クラスが提供されています。`Eval` のインスタンスは、`Eval.now`, `Eval.always`, `Eval.later`　を用いて生成することができます（いわゆる Smart Constructor）：

```scala
import cats.Eval

 val x = Eval.now {
   println("Computing X")
   math.random
 }
 // Computing X
 // x: Eval[Double] = Now(0.681816469770503)

 x.value // first access
 // res10: Double = 0.681816469770503
 x.value // second access
 // res11: Double = 0.681816469770503

 val y = Eval.always {
   println("Computing Y")
   math.random
 }
 // y: Eval[Double] = cats.Always@414a351

 y.value // first access
 // Computing Y
 // res12: Double = 0.09982997820703643
 y.value // second access
 // Computing Y
 // res13: Double = 0.34240334819463436

 val z = Eval.later {
   println("Computing Z")
   math.random
 }
 // z: Eval[Double] = cats.Later@b0a344a

 z.value // first access
 // Computing Z
 // res14: Double = 0.3604236919233441
 z.value // second access
 // res15: Double = 0.3604236919233441
```

コードを実行した際の挙動（コメントアウトされている箇所）からもわかるとおり、三種類のスマートコンストラクタはそれぞれ `val`, `def`, `lazy val` に対応しており、`[Eval のインスタンス].value` にアクセスした際の挙動が異なっています。

私個人として、`Eval` を用いて複数の評価戦略を抽象化することのメリットは、以下の二点だと考えています：

1. `Eval` のインスタンスが `map` メソッド及び `flatMap` メソッドを持つ
2. `Eval` のインスタンスに対して for expression (fp-ts や Haskell 　の Do-notation 相当のもの)を使うことができる

以下のコードに例を示します：

```scala
val ans = for {
  a <- Eval
    .now { println("Calculating A"); 38 }
    .map(a => { println("map a to a'"); a + 2 })
  b <- Eval
    .always { println("Calculating B"); 1 }
    .map(b => { println("map b to b'"); b + 1 })
} yield {
  println("Adding A and B")
  a + b
}

println(ans.value)
// Calculating A
// map a to a'
// Calculating B
// map b to b'
// Adding A and B
// 42
```

1 については、`A => B` な関数や `A => Eval[B]` な関数があったとき、`Eval` のインスタンスがどの評価戦略を抽象化したものであっても同じように使うことができるという強みがあります。上記のコード例では実行順序をわかりやすくするために、 `Eval.now({...}).map()` に渡す式と、`Eval.always({...}).map()` に渡す式を別のものにしましたが、同じ式を渡すことも可能だったということです。

2 についてはまさにこのコードの通りで、`Eval[Int]` 型の変数 `a` と `b` 　は、その「中身」にかかわらず同じように for expression の内部で bind できる点が嬉しいです。

#### fp-ts にも `Eval` はあるの？

fp-ts に `Eval` はありません。そもそも TypeScript には `lazy val` 相当のキーワードが存在しないため、仮に fp-ts で `Eval` を提供するのであれば、`lazy val` 相当の機構を実装する必要があります。

過去に fp-ts の GitHub リポジトリに 「`Memoize` をサポートしませんか？」という旨の issue が立ったことがあるようですが[^10]、「caching はそれ単体で困難な課題であり、fp-ts が扱う範疇を超えている」という理由から close されています。

## まとめ

Scala の言語仕様（評価戦略、高カインド型、`implicit` キーワード、etc.）に着目して、Cats と fp-ts の主な違いを確認していきました。まとめというよりは私の感想になりますが、やはり言語仕様のレベルでやろうとしていないことをライブラリで頑張るのは大変なのだなと思いました。今回扱ったトピックの中では、特に高カインド型のセクションをお読みいただくとその大変さの一端が伝わるのではないかと思います。ライブラリの開発・メンテナンスをしてくださっている人々には頭が上がらないぜという気持ちです。

## 広告

この記事の一部は、私が所属している株式会社 HERP の業務時間中に執筆されました。株式会社 HERP では、就業時間の一部をテックブログのエントリ執筆に当てることができます。

株式会社 HERP では fp-ts をヘビーユースしており、また Haskell などの他の関数型プログラミング言語の採用実績もあります。Haskeller も開発チームに多く在籍しています（私は Haskell をほとんど使えませんが…🥺）。弊社の技術スタックやエンジニア組織に興味がある方は、Twitter DM や、マシュマロ（匿名で私に質問を投稿できるサービス）を通してご連絡ください。

Twitter: https://twitter.com/e_ntyo

マシュマロ： https://marshmallow-qa.com/e_ntyo

株式会社 HERP エンジニア向け採用資料： https://github.com/herp-inc/engineering-careers

また、テキストでのやり取りは面倒なのでオンラインでさくっとお話したい、という方は Meety をご利用ください。

Meety: https://meety.net/matches/obYSNNmaztSy

COVID-19 Pandemic の状況が改善次第、ぜひランチなどもいたしましょう。皆様とお話ができることを楽しみにしております。

ここまで記事をお読み頂き本当にありがとうございました！



[^1]: iintyo on Twitter: "社内で "Scala with Cats" の輪読会を始めました。輪読会の第一回で使用したノートを HERP TechNote で公開しております📓 https://scrapbox.io/herp-technote/Scala_with_Cats_%E8%BC%AA%E8%AA%AD%E4%BC%9A" https://twitter.com/e_ntyo/status/1395296546346868737
[^2]: "Who's using fp-ts? · Issue #1124 · gcanti/fp-ts" https://github.com/gcanti/fp-ts/issues/1124#issuecomment-583457360
[^3]: Cats は最初のリリース（the first non-snapshot version of the Cats library）が[2015 年 11 月](https://github.com/typelevel/cats/releases/tag/v0.1.2)で、fp-ts の最初のリリース（Initial experimental release）は [2017 年 2 月](https://github.com/gcanti/fp-ts/releases/tag/0.0.1)でした。また、Cats には前身のライブラリとして Scalaz があり、こちらの最初のリリースは [2010 年](https://blog.softwaremill.com/figuring-out-scala-functional-programming-libraries-af8230efccb4#1a32)です。
[^4]: fp-ts のいくつかの実装は、Cats から porting されています。v2.0.0 の時点では、Semigroup と Monoid のコードに ["Adapted from https://typelevel.org/cats"](https://github.com/gcanti/fp-ts/blob/60250b9de118d4939374368ca1be665bac871769/src/Monoid.ts#L31) とありますし、思想や設計などについても Cats やその他のライブラリ・プログラミング言語から影響を受けていると思われます。
[^5]: 本当は全 11 章で、第 9 章から第 11 章までは Case Study （第 8 章までの内容を踏まえて、Real World な問題・仕様をどう Functional な実装に落とし込むか)的な内容になっており、今回の輪読会では扱いませんでした。
[^6]: 以下の内容を日本語訳し、補足を加えました。 "How to write type class instances for your data type" https://github.com/gcanti/fp-ts/blob/2.11.8/docs/guides/HKT.md
[^7]: "3. Using GHCi — Glasgow Haskell Compiler 9.0.1 User's Guide" https://downloads.haskell.org/~ghc/9.0.1/docs/html/users_guide/ghci.html#ghci-cmd-:kind
[^8]: "猫番 — 型を司るもの、カインド" https://eed3si9n.com/herding-cats/ja/Kinds.html
[^9]: この指摘は、こちらの記事で書かれていた内容です： "Encoding HKTs in TS4.1 - DEV Community 👩‍💻👨‍💻" https://dev.to/matechs/encoding-hkts-in-ts4-1-1fn2
[^10]: "Add `memoize` · Issue #1138 · gcanti/fp-ts" https://github.com/gcanti/fp-ts/issues/1138