From 0e81c3feafce8c43aa9435eb65bf936ebc2c3d10 Mon Sep 17 00:00:00 2001 From: Valentin Willscher Date: Fri, 17 Apr 2020 01:25:13 +0900 Subject: [PATCH 1/6] Add syntax for kleisli compatible function1 --- .../main/scala/cats/syntax/function1.scala | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/core/src/main/scala/cats/syntax/function1.scala b/core/src/main/scala/cats/syntax/function1.scala index c4eb747190..9f91780fd9 100644 --- a/core/src/main/scala/cats/syntax/function1.scala +++ b/core/src/main/scala/cats/syntax/function1.scala @@ -1,11 +1,19 @@ package cats package syntax +import cats.arrow.Compose +import cats.data.Kleisli + trait Function1Syntax { implicit def catsSyntaxFunction1[F[_]: Functor, A, B](fab: F[Function1[A, B]]): Function1Ops[F, A, B] = new Function1Ops[F, A, B](fab) + implicit def catsSyntaxFunction1ComposeKleisli[F[_], A, B]( + fab: Function1[A, F[B]] + )(implicit ev: Compose[Kleisli[F, *, *]]): Function1ComposeKleisliOps[F, A, B] = + new Function1ComposeKleisliOps[F, A, B](fab) + final class Function1Ops[F[_]: Functor, A, B](fab: F[Function1[A, B]]) { /** @@ -30,4 +38,17 @@ trait Function1Syntax { */ def mapApply(a: A): F[B] = Functor[F].map(fab)(_(a)) } + + final class Function1ComposeKleisliOps[F[_], A, B](f: A => F[B])(implicit CK: Compose[Kleisli[F, *, *]]) { + + /** + * Alias for `(Kleisli(f) >>> Kleisli(g)).run` or `(Kleisli(f) andThen Kleisli(g)).run` + */ + def >=>[C](g: B => F[C]): A => F[C] = CK.andThen(Kleisli(f), Kleisli(g)).run + + /** + * Alias for `(Kleisli(f) <<< Kleisli(g)).run` or `(Kleisli(f) compose Kleisli(g)).run` + */ + def <=<[C](g: C => F[A]): C => F[B] = CK.compose(Kleisli(f), Kleisli(g)).run + } } From 843ba2075e8ab54d60a6ceba9f8ab956ba7d518b Mon Sep 17 00:00:00 2001 From: Valentin Willscher Date: Fri, 17 Apr 2020 01:25:54 +0900 Subject: [PATCH 2/6] Add tests for syntax for kleisli compatible function1 --- tests/src/test/scala/cats/tests/SyntaxSuite.scala | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/SyntaxSuite.scala b/tests/src/test/scala/cats/tests/SyntaxSuite.scala index ee80114b41..277482df7c 100644 --- a/tests/src/test/scala/cats/tests/SyntaxSuite.scala +++ b/tests/src/test/scala/cats/tests/SyntaxSuite.scala @@ -2,7 +2,7 @@ package cats.tests import cats._ import cats.arrow.Compose -import cats.data.{Binested, Nested, NonEmptyChain, NonEmptyList, NonEmptySet} +import cats.data.{Binested, Kleisli, Nested, NonEmptyChain, NonEmptyList, NonEmptySet} import cats.syntax.all._ import scala.collection.immutable.{SortedMap, SortedSet} @@ -57,6 +57,15 @@ object SyntaxSuite { } + def testComposeKleisli[F[_]: Lambda[X[_] => Compose[Kleisli[X, *, *]]], A, B, C, D]: Unit = { + val x = mock[Function[A, F[B]]] + val y = mock[Function[B, F[C]]] + val z = mock[Function[C, F[D]]] + + val a = x >=> y >=> z + val b = z <=< y <=< x + } + def testEq[A: Eq]: Unit = { val x = mock[A] val y = mock[A] From fc721380ae95c1226fc30f6c0046944a99855823 Mon Sep 17 00:00:00 2001 From: Valentin Willscher Date: Fri, 17 Apr 2020 15:33:31 +0900 Subject: [PATCH 3/6] Use FlatMap constraint instead of Compose/Kleisli --- .../main/scala/cats/syntax/function1.scala | 49 ++++++++++++++----- .../test/scala/cats/tests/SyntaxSuite.scala | 20 ++++---- 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/core/src/main/scala/cats/syntax/function1.scala b/core/src/main/scala/cats/syntax/function1.scala index 9f91780fd9..6377237fa4 100644 --- a/core/src/main/scala/cats/syntax/function1.scala +++ b/core/src/main/scala/cats/syntax/function1.scala @@ -1,18 +1,15 @@ package cats package syntax -import cats.arrow.Compose -import cats.data.Kleisli - trait Function1Syntax { implicit def catsSyntaxFunction1[F[_]: Functor, A, B](fab: F[Function1[A, B]]): Function1Ops[F, A, B] = new Function1Ops[F, A, B](fab) - implicit def catsSyntaxFunction1ComposeKleisli[F[_], A, B]( + implicit def catsSyntaxFunction1FlatMap[F[_]: FlatMap, A, B]( fab: Function1[A, F[B]] - )(implicit ev: Compose[Kleisli[F, *, *]]): Function1ComposeKleisliOps[F, A, B] = - new Function1ComposeKleisliOps[F, A, B](fab) + ): Function1FlatMapOps[F, A, B] = + new Function1FlatMapOps[F, A, B](fab) final class Function1Ops[F[_]: Functor, A, B](fab: F[Function1[A, B]]) { @@ -39,16 +36,46 @@ trait Function1Syntax { def mapApply(a: A): F[B] = Functor[F].map(fab)(_(a)) } - final class Function1ComposeKleisliOps[F[_], A, B](f: A => F[B])(implicit CK: Compose[Kleisli[F, *, *]]) { + final class Function1FlatMapOps[F[_]: FlatMap, A, B](f: A => F[B]) { /** - * Alias for `(Kleisli(f) >>> Kleisli(g)).run` or `(Kleisli(f) andThen Kleisli(g)).run` + * Alias for `a => f(a).flatMap(g)` or `(Kleisli(f) andThen Kleisli(g)).run` + * + * Example: + * {{{ + * scala> import scala.util._ + * scala> import cats.implicits._ + * + * scala> val f: List[String] => Option[String] = _.headOption + * scala> val g: String => Option[Int] = str => Try(str.toInt).toOption + * scala> (f >=> g)(List("42")) + * res0: Option[Int] = Some(42) + * scala> (f >=> g)(List("abc")) + * res1: Option[Int] = None + * scala> (f >=> g)(List()) + * res2: Option[Int] = None + * }}} */ - def >=>[C](g: B => F[C]): A => F[C] = CK.andThen(Kleisli(f), Kleisli(g)).run + def >=>[C](g: B => F[C]): A => F[C] = a => FlatMap[F].flatMap(f(a))(g) /** - * Alias for `(Kleisli(f) <<< Kleisli(g)).run` or `(Kleisli(f) compose Kleisli(g)).run` + * Alias for `c => g(c).flatMap(f)` or `(Kleisli(f) compose Kleisli(g)).run` + * + * Example: + * {{{ + * scala> import scala.util._ + * scala> import cats.implicits._ + * + * scala> val f: String => Option[Int] = str => Try(str.toInt).toOption + * scala> val g: List[String] => Option[String] = _.headOption + * scala> (f <=< g)(List("42")) + * res0: Option[Int] = Some(42) + * scala> (f <=< g)(List("abc")) + * res1: Option[Int] = None + * scala> (f >=> g)(List()) + * res2: Option[Int] = None + * }}} */ - def <=<[C](g: C => F[A]): C => F[B] = CK.compose(Kleisli(f), Kleisli(g)).run + def <=<[C](g: C => F[A]): C => F[B] = c => FlatMap[F].flatMap(g(c))(f) } } diff --git a/tests/src/test/scala/cats/tests/SyntaxSuite.scala b/tests/src/test/scala/cats/tests/SyntaxSuite.scala index 277482df7c..e201f496a1 100644 --- a/tests/src/test/scala/cats/tests/SyntaxSuite.scala +++ b/tests/src/test/scala/cats/tests/SyntaxSuite.scala @@ -2,7 +2,7 @@ package cats.tests import cats._ import cats.arrow.Compose -import cats.data.{Binested, Kleisli, Nested, NonEmptyChain, NonEmptyList, NonEmptySet} +import cats.data.{Binested, Nested, NonEmptyChain, NonEmptyList, NonEmptySet} import cats.syntax.all._ import scala.collection.immutable.{SortedMap, SortedSet} @@ -57,15 +57,6 @@ object SyntaxSuite { } - def testComposeKleisli[F[_]: Lambda[X[_] => Compose[Kleisli[X, *, *]]], A, B, C, D]: Unit = { - val x = mock[Function[A, F[B]]] - val y = mock[Function[B, F[C]]] - val z = mock[Function[C, F[D]]] - - val a = x >=> y >=> z - val b = z <=< y <=< x - } - def testEq[A: Eq]: Unit = { val x = mock[A] val y = mock[A] @@ -373,10 +364,17 @@ object SyntaxSuite { val fa = a.pure[F] } - def testFlatMap[F[_]: FlatMap, A, B]: Unit = { + def testFlatMap[F[_]: FlatMap, A, B, C, D]: Unit = { val a = mock[A] val returnValue = mock[F[Either[A, B]]] val done = a.tailRecM[F, B](a => returnValue) + + val x = mock[Function[A, F[B]]] + val y = mock[Function[B, F[C]]] + val z = mock[Function[C, F[D]]] + + val b = x >=> y >=> z + val c = z <=< y <=< x } def testApplicativeError[F[_, _], E, A, B](implicit F: ApplicativeError[F[E, *], E]): Unit = { From 1010996c9777e374692b0712b069a16142c4c2b9 Mon Sep 17 00:00:00 2001 From: Valentin Willscher Date: Fri, 17 Apr 2020 15:37:25 +0900 Subject: [PATCH 4/6] Align syntax --- core/src/main/scala/cats/syntax/function1.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/syntax/function1.scala b/core/src/main/scala/cats/syntax/function1.scala index 6377237fa4..ac5ffac63d 100644 --- a/core/src/main/scala/cats/syntax/function1.scala +++ b/core/src/main/scala/cats/syntax/function1.scala @@ -6,9 +6,7 @@ trait Function1Syntax { implicit def catsSyntaxFunction1[F[_]: Functor, A, B](fab: F[Function1[A, B]]): Function1Ops[F, A, B] = new Function1Ops[F, A, B](fab) - implicit def catsSyntaxFunction1FlatMap[F[_]: FlatMap, A, B]( - fab: Function1[A, F[B]] - ): Function1FlatMapOps[F, A, B] = + implicit def catsSyntaxFunction1FlatMap[F[_]: FlatMap, A, B](fab: Function1[A, F[B]]): Function1FlatMapOps[F, A, B] = new Function1FlatMapOps[F, A, B](fab) final class Function1Ops[F[_]: Functor, A, B](fab: F[Function1[A, B]]) { From 0426d461cfa3f8c9c75d51d88db0d013dd19c72d Mon Sep 17 00:00:00 2001 From: Valentin Willscher Date: Fri, 17 Apr 2020 15:41:29 +0900 Subject: [PATCH 5/6] Fix error in doc example --- core/src/main/scala/cats/syntax/function1.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/syntax/function1.scala b/core/src/main/scala/cats/syntax/function1.scala index ac5ffac63d..f0f969a505 100644 --- a/core/src/main/scala/cats/syntax/function1.scala +++ b/core/src/main/scala/cats/syntax/function1.scala @@ -70,7 +70,7 @@ trait Function1Syntax { * res0: Option[Int] = Some(42) * scala> (f <=< g)(List("abc")) * res1: Option[Int] = None - * scala> (f >=> g)(List()) + * scala> (f <=< g)(List()) * res2: Option[Int] = None * }}} */ From 4421d75f522ae8714e7d3ebb5d07380680fc54c2 Mon Sep 17 00:00:00 2001 From: Valentin Willscher Date: Mon, 1 Jun 2020 21:42:55 +0900 Subject: [PATCH 6/6] Add primarily non-symbolic method names andThenF and composeF --- .../main/scala/cats/syntax/function1.scala | 24 +++++++++++++------ .../test/scala/cats/tests/SyntaxSuite.scala | 7 ++++-- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/core/src/main/scala/cats/syntax/function1.scala b/core/src/main/scala/cats/syntax/function1.scala index f0f969a505..7ec5ae0b1d 100644 --- a/core/src/main/scala/cats/syntax/function1.scala +++ b/core/src/main/scala/cats/syntax/function1.scala @@ -48,13 +48,18 @@ trait Function1Syntax { * scala> val g: String => Option[Int] = str => Try(str.toInt).toOption * scala> (f >=> g)(List("42")) * res0: Option[Int] = Some(42) - * scala> (f >=> g)(List("abc")) + * scala> (f andThenF g)(List("abc")) * res1: Option[Int] = None - * scala> (f >=> g)(List()) + * scala> (f andThenF g)(List()) * res2: Option[Int] = None * }}} */ - def >=>[C](g: B => F[C]): A => F[C] = a => FlatMap[F].flatMap(f(a))(g) + def andThenF[C](g: B => F[C]): A => F[C] = a => FlatMap[F].flatMap(f(a))(g) + + /** + * Alias for `f andThenF g`. + */ + @inline def >=>[C](g: B => F[C]): A => F[C] = f.andThenF(g) /** * Alias for `c => g(c).flatMap(f)` or `(Kleisli(f) compose Kleisli(g)).run` @@ -66,14 +71,19 @@ trait Function1Syntax { * * scala> val f: String => Option[Int] = str => Try(str.toInt).toOption * scala> val g: List[String] => Option[String] = _.headOption - * scala> (f <=< g)(List("42")) + * scala> (f composeF g)(List("42")) * res0: Option[Int] = Some(42) - * scala> (f <=< g)(List("abc")) + * scala> (f composeF g)(List("abc")) * res1: Option[Int] = None - * scala> (f <=< g)(List()) + * scala> (f composeF g)(List()) * res2: Option[Int] = None * }}} */ - def <=<[C](g: C => F[A]): C => F[B] = c => FlatMap[F].flatMap(g(c))(f) + def composeF[C](g: C => F[A]): C => F[B] = c => FlatMap[F].flatMap(g(c))(f) + + /** + * Alias for `f composeF g`. + */ + @inline def <=<[C](g: C => F[A]): C => F[B] = f.composeF(g) } } diff --git a/tests/src/test/scala/cats/tests/SyntaxSuite.scala b/tests/src/test/scala/cats/tests/SyntaxSuite.scala index e201f496a1..646ef86994 100644 --- a/tests/src/test/scala/cats/tests/SyntaxSuite.scala +++ b/tests/src/test/scala/cats/tests/SyntaxSuite.scala @@ -373,8 +373,11 @@ object SyntaxSuite { val y = mock[Function[B, F[C]]] val z = mock[Function[C, F[D]]] - val b = x >=> y >=> z - val c = z <=< y <=< x + val b = x.andThenF(y).andThenF(z) + val b2 = x >=> y >=> z + + val c = z.composeF(y).composeF(x) + val c2 = z <=< y <=< x } def testApplicativeError[F[_, _], E, A, B](implicit F: ApplicativeError[F[E, *], E]): Unit = {