Skip to content

Commit 29dd05a

Browse files
authored
Merge pull request #4340 from kubukoz/add-function-applyn
Add FunctionN.liftN, parLiftN
2 parents f4aec7f + 8d53b58 commit 29dd05a

File tree

3 files changed

+103
-17
lines changed

3 files changed

+103
-17
lines changed

core/src/main/scala/cats/syntax/apply.scala

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
package cats
2323
package syntax
2424

25-
trait ApplySyntax extends TupleSemigroupalSyntax {
25+
trait ApplySyntax extends TupleSemigroupalSyntax with FunctionApplySyntax {
2626
@deprecated("Kept for binary compatibility", "2.10.0")
2727
final def catsSyntaxApply[F[_], A](fa: F[A], F: Apply[F]): Apply.Ops[F, A] =
2828
new Apply.Ops[F, A] {
@@ -51,7 +51,7 @@ final class ApplyFABOps[F[_], A, B](private val fab: F[A => B]) extends AnyVal {
5151

5252
/**
5353
* @see [[Apply.ap]].
54-
*
54+
*
5555
* Example:
5656
* {{{
5757
* scala> import cats.syntax.all._
@@ -86,7 +86,7 @@ final class ApplyFABCOps[F[_], A, B, C](private val ff: F[(A, B) => C]) extends
8686

8787
/**
8888
* @see [[Apply.ap2]].
89-
*
89+
*
9090
* Example:
9191
* {{{
9292
* scala> import cats.syntax.all._
@@ -109,7 +109,7 @@ final class ApplyFABCOps[F[_], A, B, C](private val ff: F[(A, B) => C]) extends
109109
* scala> noneF.ap2(noneInt, noneInt)
110110
* res3: Option[Long] = None
111111
* }}}
112-
*
112+
*
113113
*/
114114
def ap2(fa: F[A], fb: F[B])(implicit F: Apply[F]): F[C] = F.ap2(ff)(fa, fb)
115115
}
@@ -138,7 +138,7 @@ final class ApplyOps[F[_], A](private val fa: F[A]) extends AnyVal {
138138

139139
/**
140140
* @see [[Apply.productR]].
141-
*
141+
*
142142
* Example:
143143
* {{{
144144
* scala> import cats.syntax.all._
@@ -163,13 +163,13 @@ final class ApplyOps[F[_], A](private val fa: F[A]) extends AnyVal {
163163
*
164164
* scala> invalidInt.productR(invalidBool)
165165
* res3: ErrOr[Boolean] = Invalid(Invalid int.Invalid boolean.)
166-
* }}}
166+
* }}}
167167
*/
168168
def productR[B](fb: F[B])(implicit F: Apply[F]): F[B] = F.productR(fa)(fb)
169169

170170
/**
171171
* @see [[Apply.productL]].
172-
*
172+
*
173173
* Example:
174174
* {{{
175175
* scala> import cats.syntax.all._
@@ -194,7 +194,7 @@ final class ApplyOps[F[_], A](private val fa: F[A]) extends AnyVal {
194194
*
195195
* scala> invalidInt.productL(invalidBool)
196196
* res3: ErrOr[Int] = Invalid(Invalid int.Invalid boolean.)
197-
* }}}
197+
* }}}
198198
*/
199199
def productL[B](fb: F[B])(implicit F: Apply[F]): F[A] = F.productL(fa)(fb)
200200

@@ -210,7 +210,7 @@ final class ApplyOps[F[_], A](private val fa: F[A]) extends AnyVal {
210210

211211
/**
212212
* @see [[Apply.map2]].
213-
*
213+
*
214214
* Example:
215215
* {{{
216216
* scala> import cats.syntax.all._
@@ -231,25 +231,25 @@ final class ApplyOps[F[_], A](private val fa: F[A]) extends AnyVal {
231231
*
232232
* scala> noneInt.map2(someLong)((i, l) => i.toString + l.toString)
233233
* res0: Option[String] = None
234-
* }}}
234+
* }}}
235235
*/
236236
def map2[B, C](fb: F[B])(f: (A, B) => C)(implicit F: Apply[F]): F[C] =
237237
F.map2(fa, fb)(f)
238238

239239
/**
240240
* @see [[Apply.map2Eval]].
241-
*
241+
*
242242
* Example:
243243
* {{{
244244
* scala> import cats.{Eval, Later}
245245
* scala> import cats.syntax.all._
246-
*
246+
*
247247
* scala> val bomb: Eval[Option[Int]] = Later(sys.error("boom"))
248248
* scala> val x: Option[Int] = None
249-
*
249+
*
250250
* scala> x.map2Eval(bomb)(_ + _).value
251251
* res0: Option[Int] = None
252-
* }}}
252+
* }}}
253253
*/
254254
def map2Eval[B, C](fb: Eval[F[B]])(f: (A, B) => C)(implicit F: Apply[F]): Eval[F[C]] =
255255
F.map2Eval(fa, fb)(f)

project/Boilerplate.scala

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ object Boilerplate {
3232
GenParallelArityFunctions,
3333
GenParallelArityFunctions2,
3434
GenFoldableArityFunctions,
35+
GenFunctionSyntax,
3536
GenTupleParallelSyntax,
3637
GenTupleShowInstances,
3738
GenTupleMonadInstances,
@@ -594,7 +595,7 @@ object Boilerplate {
594595
| * @groupname FoldableSlidingN foldable arity
595596
| * @groupdesc FoldableSlidingN
596597
| * Group sequential elements into fixed sized tuples by passing a "sliding window" over them.
597-
| *
598+
| *
598599
| * A foldable with fewer elements than the window size will return an empty list unlike `Iterable#sliding(size: Int)`.
599600
| * Example:
600601
| * {{{
@@ -604,12 +605,12 @@ object Boilerplate {
604605
| *
605606
| * scala> Foldable[List].sliding4((1 to 10).toList)
606607
| * 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))
607-
| *
608+
| *
608609
| * scala> Foldable[List].sliding4((1 to 2).toList)
609610
| * val res2: List[(Int, Int, Int, Int)] = List()
610611
| *
611612
| * }}}
612-
| *
613+
| *
613614
| * @groupprio FoldableSlidingN 999
614615
| *
615616
| */
@@ -621,4 +622,41 @@ object Boilerplate {
621622
"""
622623
}
623624
}
625+
626+
object GenFunctionSyntax extends Template {
627+
def filename(root: File) = root / "cats" / "syntax" / "FunctionApplySyntax.scala"
628+
629+
override def range = 2 to maxArity
630+
631+
def content(tv: TemplateVals) = {
632+
import tv._
633+
634+
val function = s"Function$arity[${`A..N`}, T]"
635+
636+
val typedParams = synVals.zip(synTypes).map { case (v, t) => s"$v: F[$t]" }.mkString(", ")
637+
638+
block"""
639+
|package cats
640+
|package syntax
641+
|
642+
|import cats.Functor
643+
|import cats.Semigroupal
644+
|
645+
|trait FunctionApplySyntax {
646+
| implicit def catsSyntaxFunction1Apply[T, A0](f: Function1[A0, T]): Function1ApplyOps[T, A0] = new Function1ApplyOps(f)
647+
- implicit def catsSyntaxFunction${arity}Apply[T, ${`A..N`}](f: $function): Function${arity}ApplyOps[T, ${`A..N`}] = new Function${arity}ApplyOps(f)
648+
|}
649+
|
650+
|private[syntax] final class Function1ApplyOps[T, A0](private val f: Function1[A0, T]) extends AnyVal with Serializable {
651+
| def liftN[F[_]: Functor](a0: F[A0]): F[T] = Functor[F].map(a0)(f)
652+
| def parLiftN[F[_]: Functor](a0: F[A0]): F[T] = Functor[F].map(a0)(f)
653+
|}
654+
|
655+
-private[syntax] final class Function${arity}ApplyOps[T, ${`A..N`}](private val f: $function) extends AnyVal with Serializable {
656+
- def liftN[F[_]: Functor: Semigroupal]($typedParams): F[T] = Semigroupal.map$arity(${`a..n`})(f)
657+
- def parLiftN[F[_]: Parallel]($typedParams): F[T] = Parallel.parMap$arity(${`a..n`})(f)
658+
-}
659+
"""
660+
}
661+
}
624662
}

tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,54 @@ object SyntaxSuite {
286286
tfa.parFlatMap(mfone)
287287
}
288288

289+
def testLiftN[F[_]: Apply, A, B, C, T] = {
290+
val fa = mock[F[A]]
291+
val fb = mock[F[B]]
292+
val fc = mock[F[C]]
293+
294+
val fapply1 = mock[A => T]
295+
296+
val result1 = fapply1.liftN(fa)
297+
298+
result1: F[T]
299+
300+
val fapply2 = mock[(A, B) => T]
301+
302+
val result2 = fapply2.liftN(fa, fb)
303+
304+
result2: F[T]
305+
306+
val fapply3 = mock[(A, B, C) => T]
307+
308+
val result3 = fapply3.liftN(fa, fb, fc)
309+
310+
result3: F[T]
311+
}
312+
313+
def testParLiftN[F[_]: Parallel: Functor, A, B, C, T] = {
314+
val fa = mock[F[A]]
315+
val fb = mock[F[B]]
316+
val fc = mock[F[C]]
317+
318+
val fapply1 = mock[A => T]
319+
320+
val result1 = fapply1.parLiftN(fa)
321+
322+
result1: F[T]
323+
324+
val fapply2 = mock[(A, B) => T]
325+
326+
val result2 = fapply2.parLiftN(fa, fb)
327+
328+
result2: F[T]
329+
330+
val fapply3 = mock[(A, B, C) => T]
331+
332+
val result3 = fapply3.parLiftN(fa, fb, fc)
333+
334+
result3: F[T]
335+
}
336+
289337
def testParallelBi[M[_], F[_], T[_, _]: Bitraverse, A, B, C, D](implicit P: Parallel.Aux[M, F]): Unit = {
290338
val tab = mock[T[A, B]]
291339
val f = mock[A => M[C]]

0 commit comments

Comments
 (0)