Skip to content

Commit 36094e7

Browse files
authored
Syntax for function1 kleisli-composition (#3396)
* Add syntax for kleisli compatible function1 * Add tests for syntax for kleisli compatible function1 * Use FlatMap constraint instead of Compose/Kleisli * Align syntax * Fix error in doc example * Add primarily non-symbolic method names andThenF and composeF
1 parent 97468fa commit 36094e7

File tree

2 files changed

+67
-1
lines changed

2 files changed

+67
-1
lines changed

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

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ trait Function1Syntax {
66
implicit def catsSyntaxFunction1[F[_]: Functor, A, B](fab: F[Function1[A, B]]): Function1Ops[F, A, B] =
77
new Function1Ops[F, A, B](fab)
88

9+
implicit def catsSyntaxFunction1FlatMap[F[_]: FlatMap, A, B](fab: Function1[A, F[B]]): Function1FlatMapOps[F, A, B] =
10+
new Function1FlatMapOps[F, A, B](fab)
11+
912
final class Function1Ops[F[_]: Functor, A, B](fab: F[Function1[A, B]]) {
1013

1114
/**
@@ -30,4 +33,57 @@ trait Function1Syntax {
3033
*/
3134
def mapApply(a: A): F[B] = Functor[F].map(fab)(_(a))
3235
}
36+
37+
final class Function1FlatMapOps[F[_]: FlatMap, A, B](f: A => F[B]) {
38+
39+
/**
40+
* Alias for `a => f(a).flatMap(g)` or `(Kleisli(f) andThen Kleisli(g)).run`
41+
*
42+
* Example:
43+
* {{{
44+
* scala> import scala.util._
45+
* scala> import cats.implicits._
46+
*
47+
* scala> val f: List[String] => Option[String] = _.headOption
48+
* scala> val g: String => Option[Int] = str => Try(str.toInt).toOption
49+
* scala> (f >=> g)(List("42"))
50+
* res0: Option[Int] = Some(42)
51+
* scala> (f andThenF g)(List("abc"))
52+
* res1: Option[Int] = None
53+
* scala> (f andThenF g)(List())
54+
* res2: Option[Int] = None
55+
* }}}
56+
*/
57+
def andThenF[C](g: B => F[C]): A => F[C] = a => FlatMap[F].flatMap(f(a))(g)
58+
59+
/**
60+
* Alias for `f andThenF g`.
61+
*/
62+
@inline def >=>[C](g: B => F[C]): A => F[C] = f.andThenF(g)
63+
64+
/**
65+
* Alias for `c => g(c).flatMap(f)` or `(Kleisli(f) compose Kleisli(g)).run`
66+
*
67+
* Example:
68+
* {{{
69+
* scala> import scala.util._
70+
* scala> import cats.implicits._
71+
*
72+
* scala> val f: String => Option[Int] = str => Try(str.toInt).toOption
73+
* scala> val g: List[String] => Option[String] = _.headOption
74+
* scala> (f composeF g)(List("42"))
75+
* res0: Option[Int] = Some(42)
76+
* scala> (f composeF g)(List("abc"))
77+
* res1: Option[Int] = None
78+
* scala> (f composeF g)(List())
79+
* res2: Option[Int] = None
80+
* }}}
81+
*/
82+
def composeF[C](g: C => F[A]): C => F[B] = c => FlatMap[F].flatMap(g(c))(f)
83+
84+
/**
85+
* Alias for `f composeF g`.
86+
*/
87+
@inline def <=<[C](g: C => F[A]): C => F[B] = f.composeF(g)
88+
}
3389
}

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,10 +367,20 @@ object SyntaxSuite {
367367
val fa = a.pure[F]
368368
}
369369

370-
def testFlatMap[F[_]: FlatMap, A, B]: Unit = {
370+
def testFlatMap[F[_]: FlatMap, A, B, C, D]: Unit = {
371371
val a = mock[A]
372372
val returnValue = mock[F[Either[A, B]]]
373373
val done = a.tailRecM[F, B](a => returnValue)
374+
375+
val x = mock[Function[A, F[B]]]
376+
val y = mock[Function[B, F[C]]]
377+
val z = mock[Function[C, F[D]]]
378+
379+
val b = x.andThenF(y).andThenF(z)
380+
val b2 = x >=> y >=> z
381+
382+
val c = z.composeF(y).composeF(x)
383+
val c2 = z <=< y <=< x
374384
}
375385

376386
def testApplicativeError[F[_, _], E, A, B](implicit F: ApplicativeError[F[E, *], E]): Unit = {

0 commit comments

Comments
 (0)