All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tofu.syntax.collections.scala Maven / Gradle / Ivy

package tofu.syntax

import cats.data.{State, StateT}
import cats.free.Free
import cats.syntax._
import cats.syntax.functor._
import cats.{Applicative, FlatMap, Foldable, Monad, Traverse, TraverseFilter}
import tofu.internal.FoldableStream

object collections
    extends FoldableSyntax with TraverseFilterSyntax with FunctorFilterSyntax with TofuTraverseSyntax
    with TofuFoldableSyntax {

  final implicit class CatsTraverseSyntax[F[_], A](private val self: F[A]) extends AnyVal {
    def traverse[G[_]: Applicative, B](f: A => G[B])(implicit FT: Traverse[F]): G[F[B]] =
      FT.traverse[G, A, B](self)(f)

    def traverseTap[G[_]: Applicative, B](f: A => G[B])(implicit FT: Traverse[F]): G[F[A]] =
      FT.traverseTap[G, A, B](self)(f)

    def flatTraverse[G[_]: Applicative, B](f: A => G[F[B]])(implicit F: FlatMap[F], FT: Traverse[F]): G[F[B]] =
      FT.flatTraverse[G, A, B](self)(f)

    def mapWithIndex[B](f: (A, Int) => B)(implicit FT: Traverse[F]): F[B] =
      FT.mapWithIndex[A, B](self)(f)

    def traverseWithIndexM[G[_]: Monad, B](f: (A, Int) => G[B])(implicit FT: Traverse[F]): G[F[B]] =
      FT.traverseWithIndexM[G, A, B](self)(f)

    def zipWithIndex(implicit FT: Traverse[F]): F[(A, Int)] = FT.zipWithIndex[A](self)
  }

  final implicit class TofuCollectionsSyntax[F[_], A](private val fa: F[A]) extends AnyVal {

    /** a combination of map and scanLeft it applies a function to each element of a structure, passing an accumulating
      * parameter from left to right, and returning a final value of this accumulator together with the new structure.
      */
    def mapAccumL[B, C](start: B)(step: (B, A) => (B, C))(implicit F: Traverse[F]): (B, F[C]) =
      F.traverse(fa)(a => State((b: B) => step(b, a))).run(start).value

    /** like mapAccumL, but drop the final state
      */
    def mapAccumL_[B, C](start: B)(step: (B, A) => (B, C))(implicit F: Traverse[F]): F[C] =
      mapAccumL(start)(step)._2

    /** accumulate values, producing intermediate results initial state will posess a first item place in the traverse
      * order final state is returned as a separate result
      */
    def scanL[B](start: B)(step: (B, A) => B)(implicit F: Traverse[F]): (B, F[B]) =
      mapAccumL(start)((b, a) => (b, step(b, a)))

    /** like [[mapAccumL]] but also combines monadic effects of `G` stack-safety relies on a stack safety of G
      */
    def mapAccumM[G[_]: Monad, B, C](start: B)(step: (B, A) => G[(B, C)])(implicit F: Traverse[F]): G[(B, F[C])] =
      F.traverse(fa)(a => StateT((b: B) => step(b, a))).run(start)

    /** like [[mapAccumM]] but drop the final state
      */
    def mapAccumM_[G[_]: Monad, B, C](start: B)(step: (B, A) => G[(B, C)])(implicit F: Traverse[F]): G[F[C]] =
      mapAccumM(start)(step).map(_._2)

    /** like [[mapAccumL]] but also combines monadic effects of `G` stack-safety guaranteed via Free
      */
    def mapAccumF[G[_]: Monad, B, C](start: B)(step: (B, A) => G[(B, C)])(implicit F: Traverse[F]): G[(B, F[C])] =
      F.traverse(fa)(a => StateT((b: B) => Free.liftF(step(b, a)))).run(start).runTailRec

    /** like [[mapAccumF]] but drop the final state
      */
    def mapAccumF_[G[_]: Monad, B, C](start: B)(step: (B, A) => G[(B, C)])(implicit F: Traverse[F]): G[F[C]] =
      mapAccumF(start)(step).map(_._2)

    /** accumulate values effectfully, producing intermediate results initial state will posess a first item place in
      * the traverse order final state is returned as a separate result
      */
    def scanF[G[_]: Monad, B](start: B)(step: (B, A) => G[B])(implicit F: Traverse[F]): G[(B, F[B])] =
      mapAccumF(start)((b, a) => step(b, a).tupleLeft(b))
  }

  final implicit class TofuSequenceOps[G[_], T[_], A](private val fta: T[G[A]]) extends AnyVal {
    def sequence(implicit G: Applicative[G], T: Traverse[T]): G[T[A]] =
      T.sequence[G, A](fta)
  }

  final implicit class TofuFlatSequenceOps[G[_], T[_], A](private val fta: T[G[T[A]]]) extends AnyVal {
    def flatSequence(implicit
        G: Applicative[G],
        T: Traverse[T],
        TF: FlatMap[T],
    ): G[T[A]] = T.flatSequence[G, A](fta)
  }
}

@deprecated("use tofu.syntax.collections", since = "0.10.4")
object traverse extends TofuTraverseSyntax

trait TofuTraverseSyntax {
  final implicit def tofuTraverseSyntax[F[_], A](ta: F[A]): TraverseOps[F, A] = new TraverseOps[F, A](ta)
}

@deprecated("use tofu.syntax.collections", since = "0.10.4")
object foldable extends TofuFoldableSyntax

final class TraverseOps[F[_], A](private val ta: F[A]) extends AnyVal {
  def traverseKey[G[_]: Applicative, B](f: A => G[B])(implicit T: Traverse[F]): G[F[(A, B)]] =
    T.traverse(ta)(a => f(a).map((a, _)))

  def traverseKeyFilter[G[_]: Applicative, B](f: A => G[Option[B]])(implicit TF: TraverseFilter[F]): G[F[(A, B)]] =
    TF.traverseFilter(ta)(a => f(a).map(_.map((a, _))))
}

trait TofuFoldableSyntax {
  final implicit def tofuFoldableSyntax[F[_], A](ta: F[A]): TofuFoldableOps[F, A] = new TofuFoldableOps[F, A](ta)
}

final class TofuFoldableOps[F[_], A](private val fa: F[A]) extends AnyVal {

  /** Applies monadic transfomation, feeding source collection, until operation results in None or collection is
    * consumed
    *
    * @param initial
    *   initial state
    * @param f
    *   state transformation, None would not be continued
    * @return
    *   final achieved state or initial
    */
  def foldWhileM[G[_], S](initial: S)(f: (S, A) => G[Option[S]])(implicit F: Foldable[F], G: Monad[G]): G[S] =
    G.tailRecM((initial, FoldableStream.from(fa))) {
      case (s, FoldableStream.Empty)      => G.pure(Right(s))
      case (s, FoldableStream.Cons(h, t)) =>
        G.map(f(s, h)) {
          case None     => Right(s)
          case Some(s1) => Left((s1, t.value))
        }
    }

  /** transforms each element to another type using monadic transformation until it resutls in None
    *
    * @param f
    *   element transformation, None would not be continued
    * @return
    *   a collection of transformed elements
    */
  def takeWhileM[G[_], B](f: A => G[Option[B]])(implicit F: Foldable[F], G: Monad[G]): G[List[B]] =
    G.map(foldWhileM(List.empty[B])((acc, a) => G.map(f(a))(_.map(acc.::))))(_.reverse)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy