今日も今日とてScala with Catsを読む。
この章ではコレクションに対するイテレーションの2つの型クラスについて見ていく。
- Foldable
foldLeft
やfoldRight
の操作を抽象化したもの
- Traverse
- Applicativeを使ってfoldingよりも少ない手間でイテレートを行う高度な抽象化をしたもの
Foldable
CatsのFoldable
はfoldLeft
やfoldRight
を型クラスに抽象化したもの。
Foldable
のインスタンスはこの2つのメソッドが定義し、多くの派生メソッドを継承している。
import cats.Foldable object Playground1 extends App { val ints = List(1, 2, 3) println(Foldable[List].foldLeft(ints, 0)(_ + _)) }
Folding Right
Foldable
のfoldRight
の定義は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
Foldable
はfoldLeft
の上に定義された多くの便利メソッドを提供してくれる。
それらの多くは標準ライブラリにある馴染みのあるメソッドを模倣したもの: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)) }