val foo: IO[List[Int]] = List(IO.pure(100)).sequence
Где я могу найти реализацию метода sequence? Я предполагаю, что для cats.effect.IO существует реализация класса типов Traverse, и где я могу найти эту реализацию? Если я ошибаюсь, как работает foo выше? Спасибо





Для Traverse нет IO, ну, я думаю, что нет, и если он есть, это не относится к этому примеру.
Однако существует Traverse для List и Applicative для IO, и поскольку IO также образует Monad, то этот делегат для flatMap.
В общем, мы знаем, что:
def sequence[A, F[_], G[_]](fga: F[G[A]])(using Traverse[F], Applicative[G]): G[F[A]] =
traverse(fga)(identity)
trait Traverse[F[_]]:
def traverse[A, B, G[_]](fa: F[A])(f: A => G[B])(using Applicative[G]): G[F[B]]
А для List реализация traverse следующая:
given Traverse[List] with
override def traverse[A, B, G[_]](fa: List[A])(f: A => G[B])(Applicative[G]): G[List[B]] =
fa.foldLeft(Appliative[G].pure(List.empty[B])) {
case (accG: G[List[B]], a: A) =>
val gb: G[B] = f(a)
Appliative[G].map2(accG, gb) {
case (acc: List[B], b: B) =>
b :: acc
}
}.map(_.reverse) // Or you could use foldRight or another more optimal strategy but that is besides the point.
А еще мы знаем, что когда есть Monad[G], то:
trait Monad[G[_]] extends Applicative[G]:
override def map2[A, B, C](ga: G[A], gb: G[B])(f: (A, B) => C): G[C] =
for
a <- ga
b <- gb
c = f(a, b)
yield c
Другими словами, это становится просто обходом List с использованием flatMaps.
Соответствующие источники:
Traverse[List]: https://github.com/typelevel/cats/blob/main/core/src/main/scala/cats/instances/list.scala#L120Async[IO]: https://github.com/typelevel/cats-effect/blob/1ba83f03445ff1cef778c3876486c5bb2b06350a/core/shared/src/main/scala/cats/effect/IO.scala#L1920map2 для Monad: https://github.com/typelevel/cats/blob/47fbad764ad8026d4e9f8999c7f6233c666f0df8/core/src/main/scala/cats/FlatMap.scala#L113Реальные реализации, конечно, более оптимизированы и немного сложнее, чем те, которые я использовал. Но концептуально они эквивалентны.
Я добавлю к ответу Луиса, что вы всегда можете увидеть, как разрешаются неявные выражения, например, с помощью scalacOptions += "-Vprint:typer"
Можно ли в scala 2 или 3 отлаживать процесс неявного разрешения во время выполнения?
Для
val foo: IO[List[Int]] = List(IO.pure(100)).sequence
это печатает
https://scastie.scala-lang.org/CD03j1FTSzGK61i5UivDRg
//val foo: cats.effect.IO[List[Int]] = cats.syntax.`package`.traverse.toTraverseOps[List, cats.effect.IO[Int]](scala.`package`.List.apply[cats.effect.IO[Int]](cats.effect.IO.pure[Int](100)))(cats.UnorderedFoldable.catsTraverseForList).sequence[cats.effect.IO, Int](scala.<:<.refl[cats.effect.IO[Int]], cats.effect.IO.asyncForIO);
Итак, он обессахарен, как
val foo: IO[List[Int]] = cats.syntax.traverse.toTraverseOps[List, cats.effect.IO[Int]](
scala.List.apply[cats.effect.IO[Int]](cats.effect.IO.pure[Int](100))
)(cats.UnorderedFoldable.catsTraverseForList)
.sequence[cats.effect.IO, Int](scala.<:<.refl[cats.effect.IO[Int]], effect.IO.asyncForIO)
Итак, экземпляр Traverse (Traverse[List]) — это cats.UnorderedFoldable.catsTraverseForList, а экземпляр Applicative (Applicative[IO]) — это cats.effect.IO.asyncForIO.
Эти экземпляры (Traverse[List], Applicative[IO]) — это то, что вам нужно, чтобы преобразовать List[IO[A]] в IO[List[A]]. Экземпляры Traverse[IO], Applicative[List] понадобятся, если вы наоборот захотите преобразовать IO[List[A]] в List[IO[A]].
Посмотрите, почему нет экземпляра Traverse[IO] (и Foldable[IO]): Можно ли секвенировать файл Cats.effect.IO, т. е. находится ли он в классе типов Traverse?
Спасибо за подробное объяснение