Skip to content

Commit

Permalink
Merge branch 'master' into representable-functor
Browse files Browse the repository at this point in the history
  • Loading branch information
kailuowang authored Jul 3, 2018
2 parents 0ff7eb9 + b77c388 commit c2c55ea
Show file tree
Hide file tree
Showing 12 changed files with 217 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .jvmopts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-Dfile.encoding=UTF8
-Xms1G
-Xmx6G
-XX:MaxPermSize=512M
-XX:MaxMetaspaceSize=512M
-XX:ReservedCodeCacheSize=250M
-XX:+TieredCompilation
-XX:-UseGCOverheadLimit
Expand Down
23 changes: 16 additions & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,23 @@ Cats cross-compiles to both JVM and Javascript(JS). If you are not used to
working with cross-compiling builds, the first things that you will notice is that
builds:

* Will take longer: To build JVM only, just use the `catsJVM`, or `catsJS` for

JS only. And if you want the default project to be `catsJVM`, just copy the
* Will take longer: To build JVM only, just use the `catsJVM`, or `catsJS`
for JS only. And if you want the default project to be `catsJVM`, just copy the
file `scripts/sbtrc-JVM` to `.sbtrc` in the root directory.

* May run out of memory: We suggest you use
[Paul Philips's sbt script](https://github.com/paulp/sbt-extras) that will use the settings from Cats.

### Editor Setup Tips

**IntelliJ**

- Be warned, IntelliJ is currently not 100% accurate at reporting compilation errors, there *will* be cases that it reports errors incorrectly. If you simply don't want to see the errors, a quick an easy work around is to disable *Type-Aware Highlighting* by clicking the `[T]` icon in the bottom toolbar.

- There is an open [issue](https://github.com/typelevel/cats/issues/2152) with the IntelliJ scala plugin, which prevents it from configuring similacrum correctly when importing the cats project. The work around for this issue is to set `val CompileTime = Provided` in `build.sbt`. Note: Be careful not to commit this change.

- IntelliJ does have [support](https://blog.jetbrains.com/scala/2015/07/31/inline-refactoring-for-type-aliases-and-kind-projector-support/) for kind-projector. However, it is not always seamless. If you are unable to get IntelliJ to recognise the special symbols that kind-project provides, such as `?` `Lambda[X => G[F[A]]]` or `λ[X => G[F[A]]]` try upgrading to the early access preview (EAP) version of the scala plugin. This can be done under `Settings > Languages & Frameworks > Scala > Updates`

### Write code

[See guidelines](/cats/guidelines.html).
Expand Down Expand Up @@ -185,13 +194,13 @@ run `sbt docs/makeMicrosite`

### Previewing the site

1. Install jekyll locally, depending on your platform, you might do this with:
1. Install jekyll locally. Depending on your platform, you might do this with:

yum install jekyll
`yum install jekyll`

apt-get install ruby-full; gem install jekyll
`apt-get install ruby-full; gem install jekyll`

gem install jekyll
`gem install jekyll`

2. In a shell, navigate to the generated site directory in `docs/target/site`

Expand Down
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ lazy val docSettings = Seq(
micrositeHomepage := "http://typelevel.org/cats/",
micrositeBaseUrl := "cats",
micrositeDocumentationUrl := "/cats/api/cats/index.html",
micrositeDocumentationLabelDescription := "API Documentation",
micrositeGithubOwner := "typelevel",
micrositeExtraMdFiles := Map(
file("CONTRIBUTING.md") -> ExtraMdFileConfig(
Expand Down
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)
}
}
15 changes: 15 additions & 0 deletions core/src/main/scala/cats/data/OptionT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@ private[data] sealed abstract class OptionTInstances1 extends OptionTInstances2

implicit def catsDataEqForOptionT[F[_], A](implicit F0: Eq[F[Option[A]]]): Eq[OptionT[F, A]] =
new OptionTEq[F, A] { implicit val F = F0 }

implicit def catsDataMonadErrorMonadForOptionT[F[_]](implicit F0: Monad[F]): MonadError[OptionT[F, ?], Unit] =
new OptionTMonadErrorMonad[F] { implicit val F = F0 }
}

private[data] sealed abstract class OptionTInstances2 extends OptionTInstances3 {
Expand Down Expand Up @@ -287,6 +290,18 @@ private[data] trait OptionTMonad[F[_]] extends Monad[OptionT[F, ?]] {
)))
}

private[data] trait OptionTMonadErrorMonad[F[_]] extends MonadError[OptionT[F, ?], Unit] with OptionTMonad[F] {
implicit def F: Monad[F]

override def raiseError[A](e: Unit): OptionT[F, A] = OptionT.none

override def handleErrorWith[A](fa: OptionT[F, A])(f: Unit => OptionT[F, A]): OptionT[F, A] =
OptionT(F.flatMap(fa.value) {
case s @ Some(_) => F.pure(s)
case None => f(()).value
})
}

private trait OptionTMonadError[F[_], E] extends MonadError[OptionT[F, ?], E] with OptionTMonad[F] {
override def F: MonadError[F, E]

Expand Down
8 changes: 8 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,7 @@ 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 catsLawsArbitraryForRepresentableStore[F[_], S, A](implicit
R: Representable.Aux[F, S],
ArbS: Arbitrary[S],
Expand All @@ -268,6 +269,13 @@ object arbitrary extends ArbitraryInstances0 {
implicit def catsLawsCogenForRepresentableStore[F[_]: Representable, S, A](implicit CA: Cogen[A]): Cogen[RepresentableStore[F, S, A]] = {
CA.contramap(_.extract)
}

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
7 changes: 7 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,7 +3,10 @@ package laws
package discipline

import catalysts.Platform

import cats.data.RepresentableStore
import cats.Eq
import cats.data.AndThen
import cats.instances.boolean._
import cats.instances.int._
import cats.instances.string._
Expand All @@ -29,6 +32,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
2 changes: 1 addition & 1 deletion project/Boilerplate.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ object Boilerplate {
GenTupleParallelSyntax
)

val header = "// auto-generated boilerplate" // TODO: put something meaningful here?
val header = "// auto-generated boilerplate by /project/Boilerplate.scala" // TODO: put something meaningful here?


/** Returns a seq of the generated files. As a side-effect, it actually generates them... */
Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.1.5
sbt.version=1.1.6
4 changes: 2 additions & 2 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.9.3")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.22")
addSbtPlugin("com.github.tkawachi" % "sbt-doctest" % "0.8.0")
addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.0")
addSbtPlugin("com.47deg" % "sbt-microsites" % "0.7.17")
addSbtPlugin("com.47deg" % "sbt-microsites" % "0.7.20")
addSbtPlugin("com.dwijnand" % "sbt-travisci" % "1.1.1")
addSbtPlugin("org.lyranthe.sbt" % "partial-unification" % "1.1.0")
addSbtPlugin("org.lyranthe.sbt" % "partial-unification" % "1.1.1")
addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.6.4")
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
7 changes: 7 additions & 0 deletions tests/src/test/scala/cats/tests/OptionTSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,13 @@ class OptionTSuite extends CatsSuite {
checkAll("Semigroup[OptionT[ListWrapper, Int]]", SerializableTests.serializable(Semigroup[OptionT[ListWrapper, Int]]))
}

{
// MonadError instance where F has a Monad
implicit val F = ListWrapper.monad
checkAll("OptionT[ListWrapper, Int]", MonadErrorTests[OptionT[ListWrapper, ?], Unit].monadError[Int, Int, Int])
checkAll("MonadError[OptionT[List, ?]]", SerializableTests.serializable(MonadError[OptionT[ListWrapper, ?], Unit]))
}

test("fold and cata consistent") {
forAll { (o: OptionT[List, Int], s: String, f: Int => String) =>
o.fold(s)(f) should === (o.cata(s, f))
Expand Down

0 comments on commit c2c55ea

Please sign in to comment.