Skip to content

Commit

Permalink
Make cats.data.AndThen public (#2297)
Browse files Browse the repository at this point in the history
* Make AndThen public

* Rename instances for AndThen

* Rename instances for AndThen

* Fix AndThen ScalaDoc

* Fix AndThen tests, naming, according to review

* rename instance class according to convention.

* fix instance class name
  • Loading branch information
alexandru authored and kailuowang committed Jul 3, 2018
1 parent 698beb8 commit b77c388
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 4 deletions.
127 changes: 124 additions & 3 deletions core/src/main/scala/cats/data/AndThen.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package cats.data
package cats
package data

import java.io.Serializable
import cats.arrow.{ArrowChoice, CommutativeArrow}


/**
* A function type of a single input that can do function composition
Expand All @@ -16,8 +19,48 @@ import java.io.Serializable
* // This should not trigger stack overflow ;-)
* f(0)
* }}}
*
* This can be used to build stack safe data structures that make
* use of lambdas. The perfect candidates for usage with `AndThen`
* are the data structures using a signature like this (where
* `F[_]` is a monadic type):
*
* {{{
* A => F[B]
* }}}
*
* As an example, if we described this data structure, the naive
* solution for that `map` is stack unsafe:
*
* {{{
* case class Resource[F[_], A, B](
* acquire: F[A],
* use: A => F[B],
* release: A => F[Unit]) {
*
* def flatMap[C](f: B => C)(implicit F: Functor[F]): Resource[F, A, C] = {
* Resource(
* ra.acquire,
* // Stack Unsafe!
* a => ra.use(a).map(f),
* ra.release)
* }
* }
* }}}
*
* To describe a `flatMap` operation for this data type, `AndThen`
* can save the day:
*
* {{{
* def flatMap[C](f: B => C)(implicit F: Functor[F]): Resource[F, A, C] = {
* Resource(
* ra.acquire,
* AndThen(ra.use).andThen(_.map(f)),
* ra.release)
* }
* }}}
*/
private[cats] sealed abstract class AndThen[-T, +R]
sealed abstract class AndThen[-T, +R]
extends (T => R) with Product with Serializable {

import AndThen._
Expand Down Expand Up @@ -97,7 +140,7 @@ private[cats] sealed abstract class AndThen[-T, +R]
"AndThen$" + System.identityHashCode(this)
}

private[cats] object AndThen {
object AndThen extends AndThenInstances0 {
/** Builds an [[AndThen]] reference by wrapping a plain function. */
def apply[A, B](f: A => B): AndThen[A, B] =
f match {
Expand All @@ -124,3 +167,81 @@ private[cats] object AndThen {
*/
private final val fusionMaxStackDepth = 127
}

private[data] abstract class AndThenInstances0 extends AndThenInstances1 {
/**
* [[cats.Monad]] instance for [[AndThen]].
*/
implicit def catsDataMonadForAndThen[T]: Monad[AndThen[T, ?]] =
new Monad[AndThen[T, ?]] {
// Piggybacking on the instance for Function1
private[this] val fn1 = instances.all.catsStdMonadForFunction1[T]

def pure[A](x: A): AndThen[T, A] =
AndThen(fn1.pure[A](x))

def flatMap[A, B](fa: AndThen[T, A])(f: A => AndThen[T, B]): AndThen[T, B] =
AndThen(fn1.flatMap(fa)(f))

override def map[A, B](fa: AndThen[T, A])(f: A => B): AndThen[T, B] =
AndThen(f).compose(fa)

def tailRecM[A, B](a: A)(f: A => AndThen[T, Either[A, B]]): AndThen[T, B] =
AndThen(fn1.tailRecM(a)(f))
}

/**
* [[cats.ContravariantMonoidal]] instance for [[AndThen]].
*/
implicit def catsDataContravariantMonoidalForAndThen[R : Monoid]: ContravariantMonoidal[AndThen[?, R]] =
new ContravariantMonoidal[AndThen[?, R]] {
// Piggybacking on the instance for Function1
private[this] val fn1 = instances.all.catsStdContravariantMonoidalForFunction1[R]

def unit: AndThen[Unit, R] =
AndThen(fn1.unit)

def contramap[A, B](fa: AndThen[A, R])(f: B => A): AndThen[B, R] =
fa.compose(f)

def product[A, B](fa: AndThen[A, R], fb: AndThen[B, R]): AndThen[(A, B), R] =
AndThen(fn1.product(fa, fb))
}

/**
* [[cats.arrow.ArrowChoice ArrowChoice]] and
* [[cats.arrow.CommutativeArrow CommutativeArrow]] instances
* for [[AndThen]].
*/
implicit val catsDataArrowForAndThen: ArrowChoice[AndThen] with CommutativeArrow[AndThen] =
new ArrowChoice[AndThen] with CommutativeArrow[AndThen] {
// Piggybacking on the instance for Function1
private[this] val fn1 = instances.all.catsStdInstancesForFunction1

def choose[A, B, C, D](f: AndThen[A, C])(g: AndThen[B, D]): AndThen[Either[A, B], Either[C, D]] =
AndThen(fn1.choose(f)(g))

def lift[A, B](f: A => B): AndThen[A, B] =
AndThen(f)

def first[A, B, C](fa: AndThen[A, B]): AndThen[(A, C), (B, C)] =
AndThen(fn1.first(fa))

override def split[A, B, C, D](f: AndThen[A, B], g: AndThen[C, D]): AndThen[(A, C), (B, D)] =
AndThen(fn1.split(f, g))

def compose[A, B, C](f: AndThen[B, C], g: AndThen[A, B]): AndThen[A, C] =
f.compose(g)
}
}

private[data] abstract class AndThenInstances1 {
/**
* [[cats.Contravariant]] instance for [[AndThen]].
*/
implicit def catsDataContravariantForAndThen[R]: Contravariant[AndThen[?, R]] =
new Contravariant[AndThen[?, R]] {
def contramap[T1, T0](fa: AndThen[T1, R])(f: T0 => T1): AndThen[T0, R] =
fa.compose(f)
}
}
5 changes: 5 additions & 0 deletions laws/src/main/scala/cats/laws/discipline/Arbitrary.scala
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,11 @@ object arbitrary extends ArbitraryInstances0 {
F: Arbitrary[(E, SA) => F[(L, SB, A)]]): Arbitrary[IndexedReaderWriterStateT[F, E, L, SA, SB, A]] =
Arbitrary(F.arbitrary.map(IndexedReaderWriterStateT(_)))

implicit def catsLawsArbitraryForAndThen[A, B](implicit F: Arbitrary[A => B]): Arbitrary[AndThen[A, B]] =
Arbitrary(F.arbitrary.map(AndThen(_)))

implicit def catsLawsCogenForAndThen[A, B](implicit F: Cogen[A => B]): Cogen[AndThen[A, B]] =
Cogen((seed, x) => F.perturb(seed, x))
}

private[discipline] sealed trait ArbitraryInstances0 {
Expand Down
6 changes: 6 additions & 0 deletions laws/src/main/scala/cats/laws/discipline/Eq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package laws
package discipline

import catalysts.Platform
import cats.Eq
import cats.data.AndThen
import cats.instances.boolean._
import cats.instances.int._
import cats.instances.string._
Expand All @@ -28,6 +30,10 @@ object eq {
}
}

/** `Eq[AndThen]` instance, built by piggybacking on [[catsLawsEqForFn1]]. */
implicit def catsLawsEqForAndThen[A, B](implicit A: Arbitrary[A], B: Eq[B]): Eq[AndThen[A, B]] =
Eq.instance(catsLawsEqForFn1[A, B].eqv(_, _))

/**
* Create an approximation of Eq[(A, B) => C] by generating 100 values for A and B
* and comparing the application of the two functions.
Expand Down
35 changes: 34 additions & 1 deletion tests/src/test/scala/cats/tests/AndThenSuite.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,42 @@
package cats.tests
package cats
package tests

import catalysts.Platform
import cats.data._
import cats.kernel.laws.discipline.SerializableTests
import cats.laws.discipline._
import cats.arrow._
import cats.laws.discipline.eq._
import cats.laws.discipline.arbitrary._

class AndThenSuite extends CatsSuite {
{
implicit val iso = SemigroupalTests.Isomorphisms.invariant[AndThen[Int, ?]]
checkAll("AndThen[Int, Int]", SemigroupalTests[AndThen[Int, ?]].semigroupal[Int, Int, Int])
checkAll("Semigroupal[AndThen[Int, ?]]", SerializableTests.serializable(Semigroupal[AndThen[Int, ?]]))
}

{
implicit val iso = SemigroupalTests.Isomorphisms.invariant[AndThen[?, Int]]
checkAll("AndThen[Int, Int]", ContravariantMonoidalTests[AndThen[?, Int]].contravariantMonoidal[Int, Int, Int])
checkAll("ContravariantMonoidal[AndThen[?, Int]]", SerializableTests.serializable(ContravariantMonoidal[AndThen[?, Int]]))
}

checkAll("AndThen[Int, Int]", MonadTests[AndThen[Int, ?]].monad[Int, Int, Int])
checkAll("Monad[AndThen[Int, ?]]", SerializableTests.serializable(Monad[AndThen[Int, ?]]))

checkAll("AndThen[Int, Int]", CommutativeArrowTests[AndThen].commutativeArrow[Int, Int, Int, Int, Int, Int])
checkAll("Arrow[AndThen]", SerializableTests.serializable(CommutativeArrow[AndThen]))

checkAll("AndThen[Int, Int]", ChoiceTests[AndThen].choice[Int, Int, Int, Int])
checkAll("Choice[AndThen]", SerializableTests.serializable(Choice[AndThen]))

checkAll("AndThen[Int, Int]", ArrowChoiceTests[AndThen].arrowChoice[Int, Int, Int, Int, Int, Int])
checkAll("ArrowChoice[AndThen]", SerializableTests.serializable(ArrowChoice[AndThen]))

checkAll("AndThen[Int, Int]", ContravariantTests[AndThen[?, Int]].contravariant[Int, Int, Int])
checkAll("Contravariant[AndThen[?, Int]]", SerializableTests.serializable(Contravariant[AndThen[?, Int]]))

test("compose a chain of functions with andThen") {
check { (i: Int, fs: List[Int => Int]) =>
val result = fs.map(AndThen(_)).reduceOption(_.andThen(_)).map(_(i))
Expand Down

0 comments on commit b77c388

Please sign in to comment.