Skip to content

Commit

Permalink
Merge branch 'master' into applicative_if
Browse files Browse the repository at this point in the history
  • Loading branch information
kailuowang committed Jan 12, 2019
2 parents 1d627c5 + b4d1ad1 commit 2517183
Show file tree
Hide file tree
Showing 22 changed files with 262 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ install:
notifications:
webhooks:
urls:
- https://webhooks.gitter.im/e/2d5ea16a2f66f60a590b
- ${GITTER_WEBHOOK_URL}
on_success: change
on_failure: always
on_start: false
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ Here's a (non-exhaustive) list of companies that use Cats in production. Don't s
- [Apple Inc. (FEAR team)](https://news.ycombinator.com/item?id=16969118)
- [AutoScout24](https://www.autoscout24.com)
- [Avast](https://avast.com)
- [BabylonHealth](https://www.babylonhealth.com/)
- [Banno Group inside of Jack Henry & Associates](https://banno.com/)
- [Basefarm](https://basefarm.com/)
- [buildo](https://buildo.io)
Expand Down
20 changes: 19 additions & 1 deletion core/src/main/scala/cats/data/Chain.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cats
package data

import Chain._
import cats.kernel.instances.StaticMethods

import scala.annotation.tailrec
import scala.collection.immutable.SortedMap
Expand Down Expand Up @@ -394,7 +395,16 @@ sealed abstract class Chain[+A] {
builder.result
}

def hash[AA >: A](implicit hashA: Hash[AA]): Int = StaticMethods.orderedHash((this: Chain[AA]).iterator)

override def toString: String = show(Show.show[A](_.toString))

override def equals(o: Any): Boolean =
if (o.isInstanceOf[Chain[_]])
(this: Chain[Any]).===(o.asInstanceOf[Chain[Any]])(Eq.fromUniversalEquals[Any])
else false

override def hashCode: Int = hash(Hash.fromUniversalHashCode[A])
}

object Chain extends ChainInstances {
Expand Down Expand Up @@ -646,7 +656,15 @@ sealed abstract private[data] class ChainInstances1 extends ChainInstances2 {
new ChainPartialOrder[A] { implicit def A: PartialOrder[A] = A0 }
}

sealed abstract private[data] class ChainInstances2 {
sealed abstract private[data] class ChainInstances2 extends ChainInstances3 {
implicit def catsDataHashForChain[A](implicit A: Hash[A]): Hash[Chain[A]] = new Hash[Chain[A]] {
def eqv(x: Chain[A], y: Chain[A]): Boolean = x === y

def hash(fa: Chain[A]): Int = fa.hash
}
}

sealed abstract private[data] class ChainInstances3 {
implicit def catsDataEqForChain[A](implicit A: Eq[A]): Eq[Chain[A]] = new Eq[Chain[A]] {
def eqv(x: Chain[A], y: Chain[A]): Boolean = x === y
}
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/scala/cats/data/EitherT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) {
case other => F.pure(other)
})

/**
* Inverse of `MonadError#attemptT`
*/
def rethrowT(implicit F: MonadError[F, A]): F[B] =
F.rethrow(value)

def valueOr[BB >: B](f: A => BB)(implicit F: Functor[F]): F[BB] = fold(f, identity)

def valueOrF[BB >: B](f: A => F[BB])(implicit F: Monad[F]): F[BB] =
Expand Down
18 changes: 18 additions & 0 deletions core/src/main/scala/cats/data/IndexedStateT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,24 @@ sealed abstract private[data] class IndexedStateTInstances4 {
new IndexedStateTStrong[F, V] { implicit def F = F0 }
}

abstract private[data] class IndexedStateFunctions {

/**
* Instantiate an `IndexedState[S1, S2, A]`.
*
* Example:
* {{{
* scala> import cats.data.IndexedState
*
* scala> val is = IndexedState[Int, Long, String](i => (i + 1L, "Here is " + i))
* scala> is.run(3).value
* res0: (Long, String) = (4,Here is 3)
* }}}
*/
def apply[S1, S2, A](f: S1 => (S2, A)): IndexedState[S1, S2, A] =
IndexedStateT.applyF(Now((s: S1) => Now(f(s))))
}

// To workaround SI-7139 `object State` needs to be defined inside the package object
// together with the type alias.
abstract private[data] class StateFunctions {
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/scala/cats/data/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ package object data {
def tell[L](l: L): Writer[L, Unit] = WriterT.tell(l)
}

type IndexedState[S1, S2, A] = IndexedStateT[Eval, S1, S2, A]
object IndexedState extends IndexedStateFunctions

/**
* `StateT[F, S, A]` is similar to `Kleisli[F, S, A]` in that it takes an `S`
* argument and produces an `A` value wrapped in `F`. However, it also produces
Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala/cats/implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ object implicits
with instances.AllInstancesBinCompat0
with instances.AllInstancesBinCompat1
with instances.AllInstancesBinCompat2
with instances.AllInstancesBinCompat3
2 changes: 2 additions & 0 deletions core/src/main/scala/cats/instances/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,5 @@ trait AllInstancesBinCompat1
with SortedMapInstancesBinCompat0

trait AllInstancesBinCompat2 extends DurationInstances with FiniteDurationInstances

trait AllInstancesBinCompat3 extends AllCoreDurationInstances
21 changes: 19 additions & 2 deletions core/src/main/scala/cats/instances/duration.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
package cats
package instances

import scala.concurrent.duration.Duration
import scala.concurrent.duration.{Duration, FiniteDuration}

trait DurationInstances extends cats.kernel.instances.DurationInstances {
implicit val catsStdShowForDuration: Show[Duration] =

@deprecated("Left to keep binary compatibility. Use CoreDurationInstances.catsStdShowForDurationUnambiguous instead.",
"1.5.0")
val catsStdShowForDuration: Show[Duration] =
AllCoreDurationInstances.catsStdShowForDurationUnambiguous
}

private[instances] trait AllCoreDurationInstances extends CoreFiniteDurationInstances

private[instances] trait CoreFiniteDurationInstances extends CoreDurationInstances {
implicit final val catsStdShowForFiniteDurationUnambiguous: Show[FiniteDuration] =
Show.fromToString[FiniteDuration]
}

private[instances] trait CoreDurationInstances {
implicit final val catsStdShowForDurationUnambiguous: Show[Duration] =
Show.fromToString[Duration]
}

private[instances] object AllCoreDurationInstances extends AllCoreDurationInstances
9 changes: 7 additions & 2 deletions core/src/main/scala/cats/instances/finiteDuration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ package instances
import scala.concurrent.duration.FiniteDuration

trait FiniteDurationInstances extends cats.kernel.instances.FiniteDurationInstances {
implicit val catsStdShowForFiniteDuration: Show[FiniteDuration] =
Show.fromToString[FiniteDuration]

@deprecated(
"Left to keep binary compatibility. Use CoreFiniteDurationInstances.catsStdShowForFiniteDurationUnambiguous instead.",
"1.5.0"
)
val catsStdShowForFiniteDuration: Show[FiniteDuration] =
AllCoreDurationInstances.catsStdShowForFiniteDurationUnambiguous
}
4 changes: 2 additions & 2 deletions core/src/main/scala/cats/instances/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ package object instances {
object byte extends ByteInstances
object char extends CharInstances
object double extends DoubleInstances
object duration extends DurationInstances
object duration extends CoreDurationInstances with DurationInstances
object either extends EitherInstances
object eq extends EqInstances
object equiv extends EquivInstances
object float extends FloatInstances
object finiteDuration extends FiniteDurationInstances
object finiteDuration extends CoreFiniteDurationInstances with FiniteDurationInstances
object function extends FunctionInstances with FunctionInstancesBinCompat0
object future extends FutureInstances
object int extends IntInstances
Expand Down
3 changes: 2 additions & 1 deletion core/src/main/scala/cats/syntax/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,5 @@ trait AllSyntaxBinCompat4
with ApplySyntaxBinCompat0
with ParallelApplySyntax
with FoldableSyntaxBinCompat0
with ReducibleSyntaxBinCompat0
with ReducibleSyntaxBinCompat0
with BitraverseSyntaxBinCompat0
61 changes: 61 additions & 0 deletions core/src/main/scala/cats/syntax/bitraverse.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,64 @@ final class NestedBitraverseOps[F[_, _], G[_], A, B](private val fgagb: F[G[A],
def bisequence(implicit F: Bitraverse[F], G: Applicative[G]): G[F[A, B]] =
F.bisequence(fgagb)
}

trait BitraverseSyntaxBinCompat0 {
implicit final def catsSyntaxBitraverseBinCompat0[F[_, _]: Bitraverse, A, B](
fab: F[A, B]
): BitraverseOpsBinCompat0[F, A, B] =
new BitraverseOpsBinCompat0[F, A, B](fab)
implicit final def catsSyntaxLeftNestedBitraverse[F[_, _]: Bitraverse, G[_], A, B](
fgab: F[G[A], B]
): LeftNestedBitraverseOps[F, G, A, B] =
new LeftNestedBitraverseOps[F, G, A, B](fgab)
}

final class BitraverseOpsBinCompat0[F[_, _], A, B](val fab: F[A, B]) extends AnyVal {

/**
* Traverse over the left side of the structure.
* For the right side, use the standard `traverse` from [[cats.Traverse]].
*
* Example:
* {{{
* scala> import cats.implicits._
*
* scala> val intAndString: (Int, String) = (7, "test")
*
* scala> intAndString.leftTraverse(i => Option(i).filter(_ > 5))
* res1: Option[(Int, String)] = Some((7,test))
*
* scala> intAndString.leftTraverse(i => Option(i).filter(_ < 5))
* res2: Option[(Int, String)] = None
* }}}
*/
def leftTraverse[G[_], C](f: A => G[C])(implicit F: Bitraverse[F], G: Applicative[G]): G[F[C, B]] =
F.bitraverse(fab)(f, G.pure(_))
}

final class LeftNestedBitraverseOps[F[_, _], G[_], A, B](val fgab: F[G[A], B]) extends AnyVal {

/**
* Sequence the left side of the structure.
* For the right side, use the standard `sequence` from [[cats.Traverse]].
*
* Example:
* {{{
* scala> import cats.implicits._
*
* scala> val optionalErrorRight: Either[Option[String], Int] = Either.right(123)
* scala> optionalErrorRight.leftSequence
* res1: Option[Either[String, Int]] = Some(Right(123))
*
* scala> val optionalErrorLeftSome: Either[Option[String], Int] = Either.left(Some("something went wrong"))
* scala> optionalErrorLeftSome.leftSequence
* res2: Option[Either[String, Int]] = Some(Left(something went wrong))
*
* scala> val optionalErrorLeftNone: Either[Option[String], Int] = Either.left(None)
* scala> optionalErrorLeftNone.leftSequence
* res3: Option[Either[String,Int]] = None
* }}}
*/
def leftSequence(implicit F: Bitraverse[F], G: Applicative[G]): G[F[A, B]] =
F.bitraverse(fgab)(identity, G.pure(_))
}
4 changes: 4 additions & 0 deletions core/src/main/scala/cats/syntax/monadError.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ final class MonadErrorOps[F[_], E, A](private val fa: F[A]) extends AnyVal {
def ensureOr(error: A => E)(predicate: A => Boolean)(implicit F: MonadError[F, E]): F[A] =
F.ensureOr(fa)(error)(predicate)

/**
* Turns a successful value into the error returned by a given partial function if it is
* in the partial function's domain.
*/
def reject(pf: PartialFunction[A, E])(implicit F: MonadError[F, E]): F[A] =
F.flatMap(fa) { a =>
pf.andThen(F.raiseError[A]).applyOrElse(a, (_: A) => fa)
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/syntax/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ package object syntax {
object bifunctor extends BifunctorSyntax
object bifoldable extends BifoldableSyntax
object binested extends BinestedSyntax
object bitraverse extends BitraverseSyntax
object bitraverse extends BitraverseSyntax with BitraverseSyntaxBinCompat0
@deprecated("use cats.syntax.semigroupal instead", "1.0.0-RC1")
object cartesian extends SemigroupalSyntax
object choice extends ChoiceSyntax
Expand Down
9 changes: 8 additions & 1 deletion testkit/src/main/scala/cats/tests/CatsSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ package tests

import catalysts.Platform

import cats.instances.{AllInstances, AllInstancesBinCompat0, AllInstancesBinCompat1, AllInstancesBinCompat2}
import cats.instances.{
AllInstances,
AllInstancesBinCompat0,
AllInstancesBinCompat1,
AllInstancesBinCompat2,
AllInstancesBinCompat3
}
import cats.syntax.{
AllSyntax,
AllSyntaxBinCompat0,
Expand Down Expand Up @@ -47,6 +53,7 @@ trait CatsSuite
with AllInstancesBinCompat0
with AllInstancesBinCompat1
with AllInstancesBinCompat2
with AllInstancesBinCompat3
with AllSyntax
with AllSyntaxBinCompat0
with AllSyntaxBinCompat1
Expand Down
2 changes: 2 additions & 0 deletions testkit/src/main/scala/cats/tests/ListWrapper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ object ListWrapper {

def eqv[A: Eq]: Eq[ListWrapper[A]] = Eq.by(_.list)

def hash[A: Hash]: Hash[ListWrapper[A]] = Hash.by(_.list)

val traverse: Traverse[ListWrapper] = {
val F = Traverse[List]

Expand Down
34 changes: 33 additions & 1 deletion tests/src/test/scala/cats/tests/ChainSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import cats.laws.discipline.{
TraverseFilterTests,
TraverseTests
}
import cats.kernel.laws.discipline.{EqTests, MonoidTests, OrderTests, PartialOrderTests}
import cats.kernel.laws.discipline.{EqTests, HashTests, MonoidTests, OrderTests, PartialOrderTests}
import cats.laws.discipline.arbitrary._

class ChainSuite extends CatsSuite {
Expand Down Expand Up @@ -48,6 +48,12 @@ class ChainSuite extends CatsSuite {
checkAll("Eq[Chain[ListWrapper[Int]]", SerializableTests.serializable(Eq[Chain[ListWrapper[Int]]]))
}

{
implicit val hash = ListWrapper.hash[Int]
checkAll("Chain[ListWrapper[Int]]", HashTests[Chain[ListWrapper[Int]]].hash)
checkAll("Hash[Chain[ListWrapper[Int]]", SerializableTests.serializable(Hash[Chain[ListWrapper[Int]]]))
}

test("show") {
Show[Chain[Int]].show(Chain(1, 2, 3)) should ===("Chain(1, 2, 3)")
Chain.empty[Int].show should ===("Chain()")
Expand Down Expand Up @@ -186,4 +192,30 @@ class ChainSuite extends CatsSuite {
a.distinct.toList should ===(a.toList.distinct)
}
}

test("=== is consistent with == (issue #2540)") {
(Chain.one(1) |+| Chain.one(2) |+| Chain.one(3)) should be(Chain.fromSeq(List(1, 2, 3)))

forAll { (a: Chain[Int], b: Chain[Int]) =>
(a === b) should ===(a == b)
}
}

test("== returns false for non-Chains") {
forAll { (a: Chain[Int], b: Int) =>
(a == b) should ===(false)
}
}

test("== returns false for Chains of different element types") {
forAll { (a: Chain[Option[String]], b: Chain[String]) =>
(a == b) should ===(a.isEmpty && b.isEmpty)
}
}

test("Chain#hashCode is consistent with List#hashCode") {
forAll { (x: Chain[Int]) =>
x.hashCode should ===(x.toList.hashCode)
}
}
}
15 changes: 15 additions & 0 deletions tests/src/test/scala/cats/tests/EitherTSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import cats.data.EitherT
import cats.laws.discipline._
import cats.laws.discipline.arbitrary._
import cats.kernel.laws.discipline.{EqTests, MonoidTests, OrderTests, PartialOrderTests, SemigroupTests}
import scala.util.{Failure, Success, Try}

class EitherTSuite extends CatsSuite {
implicit val iso = SemigroupalTests.Isomorphisms
Expand Down Expand Up @@ -270,6 +271,20 @@ class EitherTSuite extends CatsSuite {
eithert.recoverWith { case "noteithert" => EitherT.pure[Id, String](5) } should ===(eithert)
}

test("rethrowT is inverse of attemptT when applied to a successful value") {
implicit val eqThrow: Eq[Throwable] = Eq.fromUniversalEquals
val success: Try[Int] = Success(42)

success.attemptT.rethrowT should ===(success)
}

test("rethrowT is inverse of attemptT when applied to a failed value") {
implicit val eqThrow: Eq[Throwable] = Eq.fromUniversalEquals
val failed: Try[Int] = Failure(new IllegalArgumentException("error"))

failed.attemptT.rethrowT should ===(failed)
}

test("transform consistent with value.map") {
forAll { (eithert: EitherT[List, String, Int], f: Either[String, Int] => Either[Long, Double]) =>
eithert.transform(f) should ===(EitherT(eithert.value.map(f)))
Expand Down
Loading

0 comments on commit 2517183

Please sign in to comment.