Foldable and Traverse

今日も今日とてScala with Catsを読む。

この章ではコレクションに対するイテレーションの2つの型クラスについて見ていく。

  • Foldable
    • foldLeftfoldRightの操作を抽象化したもの
  • Traverse
    • Applicativeを使ってfoldingよりも少ない手間でイテレートを行う高度な抽象化をしたもの

Foldable

CatsのFoldablefoldLeftfoldRightを型クラスに抽象化したもの。

Foldableのインスタンスはこの2つのメソッドが定義し、多くの派生メソッドを継承している。

import cats.Foldable

object Playground1 extends App {

  val ints = List(1, 2, 3)
  println(Foldable[List].foldLeft(ints, 0)(_ + _))
}

Folding Right

FoldablefoldRightの定義はfoldLeftと違い、Evalモナドが使われている。

def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]: Eval[B]

Evalを使うということは、常にスタックセーフにfoldingできるということ。

Folding with Monoids

FoldablefoldLeftの上に定義された多くの便利メソッドを提供してくれる。

それらの多くは標準ライブラリにある馴染みのあるメソッドを模倣したもの:find, exists, forall, toList, isEmpty, nonEmpty ...

これらに加えて、CatsはMonoidを使った2つのメソッドを提供する。

  • combineAlll(alias: fold)
    • Monoidを使って連続したすべての要素を結合する
  • foldMap
    • ユーザーが与えた関数をシーケンスにマッピングし、その結果をMonoidを使って結合する
import cats.instances.int._
import cats.instances.string._

Foldable[List].combineAll(List(1, 2, 3))
// 6

Foldable[List].foldMap(List(1, 2, 3))(_.toString)
// "123"

Traverse

Traverse型クラスはApplicativeを活用して、より便利で法則性のある反復処理のパターンを提供する上位のツールです。

CatsのTraverseの定義

trait Traverse[F[_]] extends Functor[F] with Foldable[F] with UnorderedTraverse[F] { self =>
  
  def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]]

  def sequence[G[_]: Applicative, A](fga: F[G[A]]): G[F[A]] =
    traverse(fga)(ga => ga)
}

CatsはList,Vector,Stream,Option,Either, その他の型のためのTraverseのインスタンスを提供する。

import cats.Traverse
import cats.instances.future._
import cats.instances.list._

import scala.concurrent.{Await, Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.DurationInt

object Playground2 extends App {

  val hostnames = List("google.com", "facebook.com", "instagram.com")

  def getUptime(hostname: String): Future[Int] =
    Future(hostname.length * 60)

  val totalUptime: Future[List[Int]] =
    Traverse[List].traverse(hostnames)(getUptime)

  println(Await.result(totalUptime, 1.second))

  val numbers = List(Future(1), Future(2), Future(3))

  val numbers2: Future[List[Int]] =
    Traverse[List].sequence(numbers)

  println(Await.result(numbers2, 1.second))
}