diff --git a/core-tests/shared/src/test/scala/zio/prelude/ForEachSpec.scala b/core-tests/shared/src/test/scala/zio/prelude/ForEachSpec.scala index 02b57205b..a8e8fe7a0 100644 --- a/core-tests/shared/src/test/scala/zio/prelude/ForEachSpec.scala +++ b/core-tests/shared/src/test/scala/zio/prelude/ForEachSpec.scala @@ -153,6 +153,13 @@ object ForEachSpec extends DefaultRunnableSpec { assert(actual)(equalTo(expected)) } }, + testM("intersperse") { + check(genList, genInt) { (as, s) => + val actual = ForEach[List].intersperse(as.map(Sum.apply), Sum(s)) + val expected = Sum(as.sum + math.max(0, as.size - 1) * s) + assert(actual)(equalTo(expected)) + } + }, testM("map") { check(genList, genIntFunction) { (as, f) => val actual = ForEach[List].map(f)(as) diff --git a/core/shared/src/main/scala/zio/prelude/Associative.scala b/core/shared/src/main/scala/zio/prelude/Associative.scala index 84346eabb..281ae5ec2 100644 --- a/core/shared/src/main/scala/zio/prelude/Associative.scala +++ b/core/shared/src/main/scala/zio/prelude/Associative.scala @@ -45,9 +45,15 @@ import scala.annotation.tailrec * can be combined in ways that are associative, commutative, and have an * identity element, supporting much more interesting modes of composition. */ -trait Associative[A] { +trait Associative[A] { self => def combine(l: => A, r: => A): A + final def intersperse(middle: A): Associative[A] = + new Associative[A] { + def combine(l: => A, r: => A): A = + self.combine(l, self.combine(middle, r)) + } + final def repeat(a: A)(n: Int): A = { @tailrec def repeatHelper(res: A, n: Int): A = diff --git a/core/shared/src/main/scala/zio/prelude/ForEach.scala b/core/shared/src/main/scala/zio/prelude/ForEach.scala index 64cdf0658..53b765f7d 100644 --- a/core/shared/src/main/scala/zio/prelude/ForEach.scala +++ b/core/shared/src/main/scala/zio/prelude/ForEach.scala @@ -167,6 +167,16 @@ trait ForEach[F[+_]] extends Covariant[F] { self => def isEmpty[A](fa: F[A]): Boolean = foldMap(fa)(_ => And.create(false)) + /** + * Folds over the elements of this collection using an associative operation + * with the middle element interspersed between every element. + */ + def intersperse[A](fa: F[A], middle: A)(implicit I: Identity[A]): A = + reduceAssociative(fa)(I.intersperse(middle)) match { + case Some(a) => a + case None => I.identity + } + /** * Lifts a function operating on values to a function that operates on each * element of a collection. @@ -416,6 +426,8 @@ trait ForEachSyntax { F.forEach_(self)(f) def isEmpty(implicit F: ForEach[F]): Boolean = F.isEmpty(self) + def intersperse[A1 >: A](middle: A1)(implicit F: ForEach[F], I: Identity[A1]): A1 = + F.intersperse(self, middle) def mapAccum[S, B](s: S)(f: (S, A) => (S, B))(implicit F: ForEach[F]): (S, F[B]) = F.mapAccum(self)(s)(f) def maxOption(implicit A: Ord[A], F: ForEach[F]): Option[A] =