Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add collections and parallel syntax #723

Merged
merged 9 commits into from
Jul 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package tofu.syntax
import cats.data.Writer
import tofu.syntax.monadic._
import cats.data.Chain
import tofu.syntax.foldable._
import tofu.syntax.collections._
import cats.instances.lazyList._
import org.scalatest.flatspec.AnyFlatSpec

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tofu.syntax
import cats.syntax._
import cats.{Applicative, Apply, FlatMap, Functor, Monad, Semigroupal}
import cats.Defer

object monadic extends TupleSemigroupalSyntax with ApplicativeSyntax with MonadSyntax {
def unit[F[_]](implicit F: Applicative[F]): F[Unit] = F.unit
Expand Down Expand Up @@ -51,6 +52,10 @@ object monadic extends TupleSemigroupalSyntax with ApplicativeSyntax with MonadS
def map2[B, Z](fb: F[B])(f: (C, B) => Z)(implicit F: Apply[F]): F[Z] = F.map2(fa, fb)(f)
def map2Eval[B, Z](fb: cats.Eval[F[B]])(f: (C, B) => Z)(implicit F: Apply[F]): cats.Eval[F[Z]] =
F.map2Eval(fa, fb)(f)

def replicate_(count: Long)(implicit F: Applicative[F], FD: Defer[F]): F[Unit] =
if (count <= 0) unit[F]
else productR(FD.defer(replicate_(count - 1)))
FunFunFine marked this conversation as resolved.
Show resolved Hide resolved
}

implicit final class TofuFlatMapOps[F[_], C](private val fa: F[C]) extends AnyVal {
Expand Down
148 changes: 148 additions & 0 deletions modules/kernel/src/main/scala/tofu/syntax/collections.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
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
FunFunFine marked this conversation as resolved.
Show resolved Hide resolved
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.11.0")
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.11.0")
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)
}
36 changes: 0 additions & 36 deletions modules/kernel/src/main/scala/tofu/syntax/foldable.scala

This file was deleted.

23 changes: 23 additions & 0 deletions modules/kernel/src/main/scala/tofu/syntax/parallel.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package tofu.syntax
import cats.syntax._
import cats.Parallel
import cats.Defer

object parallel
extends ParallelSyntax with ParallelTraverseSyntax with ParallelFlatSyntax with ParallelApplySyntax
with ParallelBitraverseSyntax with ParallelUnorderedTraverseSyntax with ParallelFoldMapASyntax
with ParallelTraverseFilterSyntax {

final implicit class TofuParallelOps[F[_], A](private val fa: F[A]) extends AnyVal {
def parReplicate(count: Int)(implicit F: Parallel[F]): F[List[A]] =
F.sequential(F.applicative.replicateA(count, F.parallel(fa)))

def parReplicate_(count: Long)(implicit F: Parallel[F], FD: Defer[F]): F[Unit] =
if (count <= 0) F.monad.unit
else
F.sequential(F.applicative.productR(F.parallel(fa))(F.parallel(FD.defer(parReplicate_(count - 1)))))

def parReplicateBatch_(count: Long, batch: Long)(implicit F: Parallel[F], FD: Defer[F]): F[Unit] =
F.monad.productR(parReplicate_(count.min(batch)))(FD.defer(parReplicateBatch_(count - batch, batch)))
}
}
14 changes: 0 additions & 14 deletions modules/kernel/src/main/scala/tofu/syntax/traverse.scala

This file was deleted.