Scalaのcatsについて知っておきたい9つのtips（和訳記事）

## はじめに
Scalaでの開発でcatsを使っているおり、使えそうなので和訳しました。（ただしところどころ日本語的に訳すのが難しい場合は、訳さなくても理解するのに影響がなければ無視し、それ以外は意訳や、直訳した上で括弧内でコメントを残しました）
この記事のもとは[9 tips about using cats in Scala you might want to know](https://blog.softwaremill.com/9-tips-about-using-cats-in-scala-you-might-want-to-know-e1bafd365f88)です。

## 本編

Scalaの関数型プログラミング（以下、FP）は言語の様々な構文と意味の特性故に、難しいのかもしれない。特に、一度は聞いたことのあるFPの中心的なライブラリの中には、いくつかの特徴的で「何かをするのに適切な方法」が存在する。それは一目瞭然だが、ガイダンスがないと始めたてのときには見つけられない。

そのため、私はこの投稿の中でScalaのFPにおけるいくつかの慣習のtipsを共有することは役立つと考えている。例や特定の名前に関してはcats用のものであるが、共通の理論的背景を持つscalazの構文でも似通っている。

### 9) Extention method constructors

おそらく最も基本的な特徴から始めていこう。それは任意の型に対する`Option`、`Either` などに変換する拡張メソッドである。すなわち：

- `Option`に対するコンストラクタメソッドの`.some`と対応する`none`
- `Either`に対する`.asRight`と`.asLeft`
- `Validated`に対する`.valid`、`.invalid`、`.validNel`、`.invalidNel`

これらを使う利点を以下の２点：

1. 間違いなくよりコンパクトで理解しやすい（なぜならメソッドチェーンの並びと同一だから）
2. これらのメソッドの返す型はそのsupertypeである

```scala
import cats.implicits._

Some("a")
// Some[String]

"a".some
// Option[String]
```

数年のうちに型のインターフェースは改善され、プログラマの不安を避けるような振る舞い（たぶん静的型付けの面倒な部分のことだと思います）をする場面は減ってきた一方で、モダンなScalaでもover-specializeing typing（これに関してはよくわからなかったです）が原因で、コンパイルエラーが未だに起きることがある。一般に"headdeskers"のようなものは`Either`で起きる（Chapter 4.4.2 of Scala with Catsのドキュメントを参照）

もう一つ覚えておくことは、`.asRight`と`.asLeft`は1つの型パラメータをもつということである。例えば"`"1".asRight[Int]`は`Either[Int, String]`である。パラメータを与えない場合は、コンパイラは推測しようとし、`Nothing`にする。そのため、これは常に何も与えないか、両方与えることをするよりも便利である。

### 8) `"*> Tales, woo-oo!"`

`*>`は`Apply`（つまり`Applicative`、`Monad`などにも）上で定義されていて、単純に「もとの演算を処理し、結果を2つ目に与えられた引数に置き換える」ということを意味する。

```scala
fa.flatMap(_ => fb)
```

なぜ一見効果のない演算子をわざわざ使うのか？それは一度`ApplicativeError`や`MonadError`を使えば、この演算子は全体の流れに対するエラーの効果を維持する、ということが分かるだろう。例として`Either`を使う：

```scala
import cats.implicites._

val success1 = "a".asRight[Int]
val success2 = "b".asRight[INt]

val failure = 400.asLeft[String]

success1 *> success2
// Right(b)

success2 *> success1
// Right(a)

success1 *> failure
// Left(400)

failure *> success1
// Left(400)
```

ご覧のように、エラーが発生したら、計算は短くなる。`*>`を使うことは`Monix`タスクや`IO`のような遅延の計算を扱う際に、頻繁に役に立つショートカットになる。

対称的な演算子である`<*`もある。つまり前のセットアップのように：

```scala
success1 <* success2
// Right(a)
```

最後に、もし演算子を使いたくなければ、そうする必要はない：

- `*>`は単純に`productR`のエイリアス
- `<*`は`productL`のエイリアス

### 7) Thoust thou even lift?

`lift`は直感的に理解するのに時間がかかる概念のうちの1つだが、一度理解すれば、至るところで見ようになるだろう。

多くのFPについての用語のように、`lift`はカテゴリー理論からの概念である。説明するのに最も良い方法は次の通り：
ある操作を与えると、その操作の型のシグニチャをより抽象的な型`F`と直接「関連付け」られるように変更する。（抽象的な物言いで微妙に何言ってるかわかりにくいですが、コード見るとなんとなく言ってることは把握できると思います）

catsでは基本的な例は`Functor`にある。

```scala
def lift[A, B](f: A => B): F[A] => F[B] = map(_)(f)
```

与えられた関数を変更し、与えられたファンクターの型`F`上で動作するようにすることを意味する。

リフト関数は、基本的に`EitherT.right`になる`EitherT.liftF`といったような、特定の型の「埋め込みコンストラクタ」と同義語であることがある。
Scaladocの例に基づくと：

```scala
import cats.data.EitherT
import cats.implicits._

EitherT.liftF("a".some)
// EitherT(Some(Right(a)))

EiterT.liftF(none[String])
// EitherT(None)
```

`lift`は実際にはScalaの標準ライブラリのもある。最も顕著な例は`PartialFunction`のものである。

```scala
val intMatcher: PartialFunction[Int, String] = {
  case 1 => "jak sie masz!"
}

val liftedIntMatcher: Int => Option[String] = intMatcher.lift

liftedIntMatcher(1)
// Some(jak sie masz!)
liftedIntMatcher(0)
// None

intMatcher(1)
// jak sie masz!
intMatcher(0)
// Exception in thread "main" scala.MatchError: 0
```

### 6) mapN

`mapN`は、役立つタプルのユーティリティ関数である。新しい事でもなければ、基本的には`|@|`、つまり["Scream"演算子構文](https://typelevel.org/cats/api/cats/syntax/SemigroupalOps.html#|@|[B](fb:F[B]):cats.syntax.SemigroupalBuilder[F]#SemigroupalBuilder2[A,B])の置き換えである。

主題に戻って、2つの要素のタプルに対する`mapN`の例を見ていく：

```scala
// where t2: Tuple2[F[A0], F[A1]]
def mapN[Z](f: (A0, A1) => Z)(
  implicit functor: Functor[F],
  semigroupal: Semigroupal[F]
): F[Z] = Semigroupal.map2(t2._1, t2._2)(f)
```

基本的に、`Semigroupal`（`product`）や`Functor`（`map`）といった任意の`F`のタプルに対してmapできるようになる。つまり：

```scala
import cats.implicits._

("a".some, "b".some).mapN(_ ++ _)
// Some(ab)

(List(1, 2), List(3, 4), List(0, 2)).mapN(_ * _ * _)
// List(0, 6, 0, 8, 0, 12, 0, 16)
```

ところで、catsではタプルで`map`と`leftMap`もまた使えることを覚えておいてほしい：

```scala
("a".some, List("b","c").mapN(_ ++ _))
// won't compile, because outer type is not the same

("a".some, List("b", "c")).leftMap(_.toList).mapN(_ ++ _)
// List(ab, ac)
```

`mapN`はcase classをインスタンス化する場合に役立つ：

```scala
case class Mead(name: String, honeyRatio: Double, agingYears: Double)

("półtorak".some, 0.5.some, 3d.some).mapN(Mead)
// Some(Mead(półtorak,0.5,3.0))
```

もちろん、普通に`for`ループを使ってもそのようなことができるだろう。しかし、シンプルな場合において`mapN`によってモナドの変換を回避できる：

```scala
import cats.effect.IO
import cats.implicits._

//interchangable with e.g. Monix's Task
type Query[T] = IO[Option[T]]

def defineMead(qName: Query[String],
               qHoneyRatio: Query[Double],
               qAgingYears: Query[Double]): Query[Mead] =
  (for {
    name       <- OptionT(qName)
    honeyRatio <- OptionT(qHoneyRatio)
    agingYears <- OptionT(qAgingYears)
  } yield Mead(name, honeyRatio, agingYears)).value

def defineMead2(qName: Query[String],
                qHoneyRatio: Query[Double],
                qAgingYears: Query[Double]): Query[Mead] =
  for {
    name       <- qName
    honeyRatio <- qHoneyRatio
    agingYears <- qAgingYears
  } yield (name, honeyRatio, agingYears).mapN(Mead)
```

どちらのメソッドも同様の結果が得られるが、後者はモナドの変換が不要になっている。

### 5) Nested

`Nested`は基本的にモナド変換の一般化された対照物である（正直訳してもよくわかりません）。その名の通り、ある状況下でネストを作る操作ができる。ここでは`.map(_.map(`の例を扱う：

```scala
import cats.implicits._
import cats.data.Nested

val someValue: Option[Either[Int, String]] = "a".asRight.some

Nested(someValue).map(_ * 3).value
//Some(Right(aaa))
```

`Functor`と違い、`Nested`は`Applicative`、`ApplicativeError`、`Traverse`からの操作を一般化する。[ここ](https://typelevel.org/cats/datatypes/nested.html)を参照。（正味参照を見たほうがいいです。）

### 4) .recover/.recoverWith/.handleError/.handleErrorWith/.valueOr

多くのFP-in-Scalaプログラミングはエラーのハンドリングを中心にローテーションをしている。`ApplicativeError`と`MonadError`に役に立つ便利なメソッドがあり、おそらく4つの最も基本的はメソッドの間にある微妙な違いを知ることは重要である。すなわち、ある`ApplicativeError`である`F[A]`を考えたとき：

- `handleError`は呼び出しポイントでのすべてのエラーを与えられた関数に従って`A`に変換する
- `recover`は似ているが、部分関数を取ることができるので、選択されたエラーもまた`A`に変換できる
- `handleErrorWith`は`handleError`と同じだが、結果が`F[A]`になるはずなので、エラーを再マッピングすることができる
- `recoverWith`は`recover`と同様だが、結果として`F[A]`も要求される

見てきたように、`handleErrorWith`か`recoverWith`は残りの方を十分に表現できる（`handleErrorWith`は`handleError`を表現でき、`recoverWith`は`recover`を表現できるということ）。それでも、そのすべては便宜上便利である。

一般的に、私は`ApplicativeError`のAPIをレビューすることを強く勧める。なぜならそれはcatsのなかで最も充実したものであり、`MonadError`と共有されているからだ。だから`cats.effect.IO`、`monix.Task`などからサポートされている。

最後に、もう一つ`Either`/`EitherT`と`Validated`と`Ior`-`.valueOr`に与えられているメソッドがある。それは基本的には`Option`に対する`.getOrElse`と同じだが、`left`側に「何かしら」を持っているクラスに対して一般化されている（`Either`型の`left`的なもがあるクラスに対して生やされているメソッドのことだと思います）。

```scala
import cats.implicits._

val failure = 400.asLeft[String]

failure.valueOr(code => s"Got error code $code")
// "Got error code 400"
```

### 3) alley-cats

[alley-cats](https://github.com/typelevel/cats/tree/master/alleycats-core)は次の2つのことを処理するための実用的な解決法である：

- 100%法則に従うわけではない型の型クラスインスタンス
- オーソドックスだがおそらく役立つであろう補助的な型クラス

歴史的に、最も顕著なプロジェクトのメンバーは`Try`のモナドインスタンスである。なぜなら、よく知るように、`Try`は致命的なエラーに関してはモナドの法則を完全に満たしているわけではないからだ。それは今catsに適切に含まれている。

しかし、それでも[モジュール](https://github.com/typelevel/cats/tree/master/alleycats-core)を見て、何か役立つものがあるかを探すことを勧める。

### 2) Be disciplined with imports

[ドキュメント](https://typelevel.org/cats/faq.html#what-imports)か特定の[書籍](https://underscore.io/books/scala-with-cats/)などからおそらく学んでいるだろうが、`cats`は特定のimportの階層を使用している：

- `cats.x`は、コア/「カーネル」タイプに対して
- `cats.data`は、`Validated`のようなデータ型、モナド変換などに対して
- `cats.syntax.x._`は、拡張メソッドのサポートをimportし、`sth.asRight`、`sht.pure`などを呼び出すことができる
- `cats.instances.x._`は、特定の型に対する様々な型クラスの実装のimplicitのスコープをimportし、例えば`sth.pure`を`"implicit not found"`エラーを起こさず、呼び出せる

そしてもちろん、単にすべての構文とすべての型クラスのインスタンスをimplicitのスコープに含めるだけの`cats.implicits._`のimportには気づいている。

実際、catsのFAQから普段catsを使った開発の際に、cats正確なimport群と共に始めるべきであるとあり、すなわち：

```scala
import cats._
import cats.data._
import cats.implicits._
```

一度ライブラリに詳しくなれば、組み合わせたりするだろう。注意したいのは、経験則として：

- `cats.syntax.x`は`x`に関係する拡張構文を与える
- `cats.instances.x`は型クラスのインスタンスを与える

例えば、ちょうど`.asRight`が欲しい場合、それは`Either`に対する拡張メソッドで、以下のようにする：

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

"a".asRight[Int]
// Right[Int, String](a)
```

一方で`Option.pure`を使いたい場合は`cats.syntax.monad.`と`cats.instances.option.`をimportする必要がある：

```scala
import cats.syntax.applicative._
import cats.instances.option._

"a".pure[Option]
// Some(a)
```

手動でimportを最適化する理由としては、自分ののScalaファイル内のimplicitのスコープを制限し、コンパイルにかかる時間をへらすことができることにある。

しかし、以下の両方に当てはまらない場合はこれをしないでほしい：

- 自分がcatsに精通している、そして
- チーム全体もその水準でライブラリに精通している

なぜか？それは：

```scala
//we don't remember where `pure` is and
//we're trying to be smart
import cats.implicits._
import cats.instances.option._

"a".pure[Option]
// could not find implicit value for parameter F: cats.Applicative[Option]
```

これは`cats.implicits`と`cats.instances.option`の両方が`cats.instances.OptionInstances`を拡張しているためである。基本的にそのような2つのimplicitのスコープをimportしてしまい、コンパイラが混乱する。

そのような場合の裏側において、implicitの階層に魔法は存在しない。それは型の拡張群によって十分に左右される。学ぶのに必要なことは、`cats.implicits`の定義に行って型のヒエラルキーを見てみることである。

それらの問題を回避するために十分な水準の理解を得るには10~20分しかかからない。適切な時間の投資である。

#### 自信がなくなるまで`import cats.implicits._`を使わない。

### 1) Keep your cats up to date on updates!

自分のFPライブラリを石でセットされたもの（微妙にわからないですね）思うだろうが、実際、`cats`と`scalaz`は積極的に開発されている。再び例としてcatsを挙げると、比較的最近に修正や改善がなされている：

- [`raiseError`を使用する際に`Throwable`を例外として課す必要はない](https://github.com/typelevel/cats/pull/2511)
- [`Duration`と`FiniteDuration`インスタンスがあり、外部のライブラリなしに`d1 > d2`が使える](https://github.com/typelevel/cats/pull/2515)
- 同様に[多くの変更や微調整がある](https://github.com/typelevel/cats/releases)

そのため、ライブラリの何のバージョンを自分がプロジェクトで使用しているかをチェックし、リリースノートについて常に把握し、それに応じて更新してほしい。

## 終わりに

初めての訳でまあまあ雑な部分もあると自覚していますが、サンプルコードを見つつ書いてあることのニュアンスが伝わって貰えれば幸いです。また公式ドキュメントと合わせて目を通しておくと記憶しやすいと思います。
あと、誤字脱字があったら申し訳ありません。