diff --git a/core/src/main/scala/cats/syntax/apply.scala b/core/src/main/scala/cats/syntax/apply.scala index df9b7d336c..dcb0d95a67 100644 --- a/core/src/main/scala/cats/syntax/apply.scala +++ b/core/src/main/scala/cats/syntax/apply.scala @@ -22,7 +22,7 @@ package cats package syntax -trait ApplySyntax extends TupleSemigroupalSyntax { +trait ApplySyntax extends TupleSemigroupalSyntax with FunctionApplySyntax { @deprecated("Kept for binary compatibility", "2.10.0") final def catsSyntaxApply[F[_], A](fa: F[A], F: Apply[F]): Apply.Ops[F, A] = new Apply.Ops[F, A] { @@ -51,7 +51,7 @@ final class ApplyFABOps[F[_], A, B](private val fab: F[A => B]) extends AnyVal { /** * @see [[Apply.ap]]. - * + * * Example: * {{{ * scala> import cats.syntax.all._ @@ -86,7 +86,7 @@ final class ApplyFABCOps[F[_], A, B, C](private val ff: F[(A, B) => C]) extends /** * @see [[Apply.ap2]]. - * + * * Example: * {{{ * scala> import cats.syntax.all._ @@ -109,7 +109,7 @@ final class ApplyFABCOps[F[_], A, B, C](private val ff: F[(A, B) => C]) extends * scala> noneF.ap2(noneInt, noneInt) * res3: Option[Long] = None * }}} - * + * */ def ap2(fa: F[A], fb: F[B])(implicit F: Apply[F]): F[C] = F.ap2(ff)(fa, fb) } @@ -138,7 +138,7 @@ final class ApplyOps[F[_], A](private val fa: F[A]) extends AnyVal { /** * @see [[Apply.productR]]. - * + * * Example: * {{{ * scala> import cats.syntax.all._ @@ -163,13 +163,13 @@ final class ApplyOps[F[_], A](private val fa: F[A]) extends AnyVal { * * scala> invalidInt.productR(invalidBool) * res3: ErrOr[Boolean] = Invalid(Invalid int.Invalid boolean.) - * }}} + * }}} */ def productR[B](fb: F[B])(implicit F: Apply[F]): F[B] = F.productR(fa)(fb) /** * @see [[Apply.productL]]. - * + * * Example: * {{{ * scala> import cats.syntax.all._ @@ -194,7 +194,7 @@ final class ApplyOps[F[_], A](private val fa: F[A]) extends AnyVal { * * scala> invalidInt.productL(invalidBool) * res3: ErrOr[Int] = Invalid(Invalid int.Invalid boolean.) - * }}} + * }}} */ def productL[B](fb: F[B])(implicit F: Apply[F]): F[A] = F.productL(fa)(fb) @@ -210,7 +210,7 @@ final class ApplyOps[F[_], A](private val fa: F[A]) extends AnyVal { /** * @see [[Apply.map2]]. - * + * * Example: * {{{ * scala> import cats.syntax.all._ @@ -231,25 +231,25 @@ final class ApplyOps[F[_], A](private val fa: F[A]) extends AnyVal { * * scala> noneInt.map2(someLong)((i, l) => i.toString + l.toString) * res0: Option[String] = None - * }}} + * }}} */ def map2[B, C](fb: F[B])(f: (A, B) => C)(implicit F: Apply[F]): F[C] = F.map2(fa, fb)(f) /** * @see [[Apply.map2Eval]]. - * + * * Example: * {{{ * scala> import cats.{Eval, Later} * scala> import cats.syntax.all._ - * + * * scala> val bomb: Eval[Option[Int]] = Later(sys.error("boom")) * scala> val x: Option[Int] = None - * + * * scala> x.map2Eval(bomb)(_ + _).value * res0: Option[Int] = None - * }}} + * }}} */ def map2Eval[B, C](fb: Eval[F[B]])(f: (A, B) => C)(implicit F: Apply[F]): Eval[F[C]] = F.map2Eval(fa, fb)(f) diff --git a/project/Boilerplate.scala b/project/Boilerplate.scala index 996e385dab..277a967cd6 100644 --- a/project/Boilerplate.scala +++ b/project/Boilerplate.scala @@ -32,6 +32,7 @@ object Boilerplate { GenParallelArityFunctions, GenParallelArityFunctions2, GenFoldableArityFunctions, + GenFunctionSyntax, GenTupleParallelSyntax, GenTupleShowInstances, GenTupleMonadInstances, @@ -594,7 +595,7 @@ object Boilerplate { | * @groupname FoldableSlidingN foldable arity | * @groupdesc FoldableSlidingN | * Group sequential elements into fixed sized tuples by passing a "sliding window" over them. - | * + | * | * A foldable with fewer elements than the window size will return an empty list unlike `Iterable#sliding(size: Int)`. | * Example: | * {{{ @@ -604,12 +605,12 @@ object Boilerplate { | * | * scala> Foldable[List].sliding4((1 to 10).toList) | * val res1: List[(Int, Int, Int, Int)] = List((1,2,3,4), (2,3,4,5), (3,4,5,6), (4,5,6,7), (5,6,7,8), (6,7,8,9), (7,8,9,10)) - | * + | * | * scala> Foldable[List].sliding4((1 to 2).toList) | * val res2: List[(Int, Int, Int, Int)] = List() | * | * }}} - | * + | * | * @groupprio FoldableSlidingN 999 | * | */ @@ -621,4 +622,41 @@ object Boilerplate { """ } } + + object GenFunctionSyntax extends Template { + def filename(root: File) = root / "cats" / "syntax" / "FunctionApplySyntax.scala" + + override def range = 2 to maxArity + + def content(tv: TemplateVals) = { + import tv._ + + val function = s"Function$arity[${`A..N`}, T]" + + val typedParams = synVals.zip(synTypes).map { case (v, t) => s"$v: F[$t]" }.mkString(", ") + + block""" + |package cats + |package syntax + | + |import cats.Functor + |import cats.Semigroupal + | + |trait FunctionApplySyntax { + | implicit def catsSyntaxFunction1Apply[T, A0](f: Function1[A0, T]): Function1ApplyOps[T, A0] = new Function1ApplyOps(f) + - implicit def catsSyntaxFunction${arity}Apply[T, ${`A..N`}](f: $function): Function${arity}ApplyOps[T, ${`A..N`}] = new Function${arity}ApplyOps(f) + |} + | + |private[syntax] final class Function1ApplyOps[T, A0](private val f: Function1[A0, T]) extends AnyVal with Serializable { + | def liftN[F[_]: Functor](a0: F[A0]): F[T] = Functor[F].map(a0)(f) + | def parLiftN[F[_]: Functor](a0: F[A0]): F[T] = Functor[F].map(a0)(f) + |} + | + -private[syntax] final class Function${arity}ApplyOps[T, ${`A..N`}](private val f: $function) extends AnyVal with Serializable { + - def liftN[F[_]: Functor: Semigroupal]($typedParams): F[T] = Semigroupal.map$arity(${`a..n`})(f) + - def parLiftN[F[_]: Parallel]($typedParams): F[T] = Parallel.parMap$arity(${`a..n`})(f) + -} + """ + } + } } diff --git a/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala b/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala index 10fe574128..2f8e82e4bd 100644 --- a/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala @@ -286,6 +286,54 @@ object SyntaxSuite { tfa.parFlatMap(mfone) } + def testLiftN[F[_]: Apply, A, B, C, T] = { + val fa = mock[F[A]] + val fb = mock[F[B]] + val fc = mock[F[C]] + + val fapply1 = mock[A => T] + + val result1 = fapply1.liftN(fa) + + result1: F[T] + + val fapply2 = mock[(A, B) => T] + + val result2 = fapply2.liftN(fa, fb) + + result2: F[T] + + val fapply3 = mock[(A, B, C) => T] + + val result3 = fapply3.liftN(fa, fb, fc) + + result3: F[T] + } + + def testParLiftN[F[_]: Parallel: Functor, A, B, C, T] = { + val fa = mock[F[A]] + val fb = mock[F[B]] + val fc = mock[F[C]] + + val fapply1 = mock[A => T] + + val result1 = fapply1.parLiftN(fa) + + result1: F[T] + + val fapply2 = mock[(A, B) => T] + + val result2 = fapply2.parLiftN(fa, fb) + + result2: F[T] + + val fapply3 = mock[(A, B, C) => T] + + val result3 = fapply3.parLiftN(fa, fb, fc) + + result3: F[T] + } + def testParallelBi[M[_], F[_], T[_, _]: Bitraverse, A, B, C, D](implicit P: Parallel.Aux[M, F]): Unit = { val tab = mock[T[A, B]] val f = mock[A => M[C]]