Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Try instances #1059

Merged
merged 7 commits into from
Jun 1, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/src/main/scala/cats/std/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ trait AllInstances
with BigIntInstances
with BigDecimalInstances
with FutureInstances
with TryInstances
with TupleInstances
10 changes: 0 additions & 10 deletions core/src/main/scala/cats/std/future.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ trait FutureInstances extends FutureInstances1 {

override def map[A, B](fa: Future[A])(f: A => B): Future[B] = fa.map(f)
}

implicit def catsStdGroupForFuture[A: Group](implicit ec: ExecutionContext): Group[Future[A]] =
new FutureGroup[A]
}

private[std] sealed trait FutureInstances1 extends FutureInstances2 {
Expand All @@ -63,10 +60,3 @@ private[cats] class FutureMonoid[A](implicit A: Monoid[A], ec: ExecutionContext)
def empty: Future[A] =
Future.successful(A.empty)
}

private[cats] class FutureGroup[A](implicit A: Group[A], ec: ExecutionContext) extends FutureMonoid[A] with Group[Future[A]] {
def inverse(fx: Future[A]): Future[A] =
fx.map(_.inverse)
override def remove(fx: Future[A], fy: Future[A]): Future[A] =
(fx zip fy).map { case (x, y) => x |-| y }
}
1 change: 1 addition & 0 deletions core/src/main/scala/cats/std/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ package object std {
object bigInt extends BigIntInstances
object bigDecimal extends BigDecimalInstances

object try_ extends TryInstances
object tuple extends TupleInstances
}
106 changes: 106 additions & 0 deletions core/src/main/scala/cats/std/try.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package cats
package std

import cats.syntax.all._
import cats.data.Xor

import scala.util.control.NonFatal
import scala.util.{Failure, Success, Try}

trait TryInstances extends TryInstances1 {

implicit def catsStdInstancesForTry: MonadError[Try, Throwable] with CoflatMap[Try] =
new TryCoflatMap with MonadError[Try, Throwable]{
def pure[A](x: A): Try[A] = Success(x)

override def pureEval[A](x: Eval[A]): Try[A] = x match {
case Now(x) => Success(x)
case _ => Try(x.value)
}

override def product[A, B](ta: Try[A], tb: Try[B]): Try[(A, B)] = (ta, tb) match {
case (Success(a), Success(b)) => Success((a, b))
case (f: Failure[_], _) => f.asInstanceOf[Try[(A, B)]]
case (_, f: Failure[_]) => f.asInstanceOf[Try[(A, B)]]
}

override def map2[A, B, Z](ta: Try[A], tb: Try[B])(f: (A, B) => Z): Try[Z] = (ta, tb) match {
case (Success(a), Success(b)) => Try(f(a, b))
case (f: Failure[_], _) => f.asInstanceOf[Try[Z]]
case (_, f: Failure[_]) => f.asInstanceOf[Try[Z]]
}

override def map2Eval[A, B, Z](ta: Try[A], tb: Eval[Try[B]])(f: (A, B) => Z): Eval[Try[Z]] =
ta match {
case f: Failure[_] => Now(f.asInstanceOf[Try[Z]])
case Success(a) => tb.map(_.map(f(a, _)))
}

def flatMap[A, B](ta: Try[A])(f: A => Try[B]): Try[B] = ta.flatMap(f)

def handleErrorWith[A](ta: Try[A])(f: Throwable => Try[A]): Try[A] =
ta.recoverWith { case t => f(t) }

def raiseError[A](e: Throwable): Try[A] = Failure(e)
override def handleError[A](ta: Try[A])(f: Throwable => A): Try[A] =
ta.recover { case t => f(t) }

override def attempt[A](ta: Try[A]): Try[Throwable Xor A] =
(ta map Xor.right) recover { case NonFatal(t) => Xor.left(t) }

override def recover[A](ta: Try[A])(pf: PartialFunction[Throwable, A]): Try[A] =
ta.recover(pf)

override def recoverWith[A](ta: Try[A])(pf: PartialFunction[Throwable, Try[A]]): Try[A] = ta.recoverWith(pf)

override def map[A, B](ta: Try[A])(f: A => B): Try[B] = ta.map(f)
}

implicit def catsStdShowForTry[A](implicit A: Show[A]): Show[Try[A]] =
new Show[Try[A]] {
def show(fa: Try[A]): String = fa match {
case Success(a) => s"Success(${A.show(a)})"
case Failure(e) => s"Failure($e)"
}
}
/**
* you may wish to do equality by making `implicit val eqT: Eq[Throwable] = Eq.allEqual`
* doing a fine grained equality on Throwable can make the code very execution
* order dependent
*/
implicit def catsStdEqForTry[A, T](implicit A: Eq[A], T: Eq[Throwable]): Eq[Try[A]] =
new Eq[Try[A]] {
def eqv(x: Try[A], y: Try[A]): Boolean = (x, y) match {
case (Success(a), Success(b)) => A.eqv(a, b)
case (Failure(a), Failure(b)) => T.eqv(a, b)
case _ => false
}
}
}

private[std] sealed trait TryInstances1 extends TryInstances2 {
implicit def catsStdMonoidForTry[A: Monoid]: Monoid[Try[A]] =
new TryMonoid[A]
}

private[std] sealed trait TryInstances2 {
implicit def catsStdSemigroupForTry[A: Semigroup]: Semigroup[Try[A]] =
new TrySemigroup[A]
}

private[cats] abstract class TryCoflatMap extends CoflatMap[Try] {
def map[A, B](ta: Try[A])(f: A => B): Try[B] = ta.map(f)
def coflatMap[A, B](ta: Try[A])(f: Try[A] => B): Try[B] = Try(f(ta))
}

private[cats] class TrySemigroup[A: Semigroup] extends Semigroup[Try[A]] {
def combine(fx: Try[A], fy: Try[A]): Try[A] =
for {
x <- fx
y <- fy
} yield x |+| y
}

private[cats] class TryMonoid[A](implicit A: Monoid[A]) extends TrySemigroup[A] with Monoid[Try[A]] {
def empty: Try[A] = Success(A.empty)
}
83 changes: 83 additions & 0 deletions tests/src/test/scala/cats/tests/TryTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package cats
package tests

import cats.laws.{ApplicativeLaws, CoflatMapLaws, FlatMapLaws, MonadLaws}
import cats.laws.discipline._

import scala.util.{Success, Try}

class TryTests extends CatsSuite {
implicit val eqThrow: Eq[Throwable] = Eq.allEqual

checkAll("Try[Int]", CartesianTests[Try].cartesian[Int, Int, Int])
checkAll("Cartesian[Try]", SerializableTests.serializable(Cartesian[Try]))

checkAll("Try[Int]", CoflatMapTests[Try].coflatMap[Int, Int, Int])
checkAll("CoflatMap[Try]", SerializableTests.serializable(CoflatMap[Try]))

checkAll("Try with Throwable", MonadErrorTests[Try, Throwable].monadError[Int, Int, Int])
checkAll("MonadError[Try, Throwable]", SerializableTests.serializable(MonadError[Try, Throwable]))

test("show") {
forAll { fs: Try[String] =>
fs.show should === (fs.toString)
}
}

// The following tests check laws which are a different formulation of
// laws that are checked. Since these laws are more or less duplicates of
// existing laws, we don't check them for all types that have the relevant
// instances.

test("Kleisli associativity") {
forAll { (l: Long,
f: Long => Try[Int],
g: Int => Try[Char],
h: Char => Try[String]) =>
val isEq = FlatMapLaws[Try].kleisliAssociativity(f, g, h, l)
isEq.lhs should === (isEq.rhs)
}
}

test("Cokleisli associativity") {
forAll { (l: Try[Long],
f: Try[Long] => Int,
g: Try[Int] => Char,
h: Try[Char] => String) =>
val isEq = CoflatMapLaws[Try].cokleisliAssociativity(f, g, h, l)
isEq.lhs should === (isEq.rhs)
}
}

test("applicative composition") {
forAll { (fa: Try[Int],
fab: Try[Int => Long],
fbc: Try[Long => Char]) =>
val isEq = ApplicativeLaws[Try].applicativeComposition(fa, fab, fbc)
isEq.lhs should === (isEq.rhs)
}
}

val monadLaws = MonadLaws[Try]

test("Kleisli left identity") {
forAll { (a: Int, f: Int => Try[Long]) =>
val isEq = monadLaws.kleisliLeftIdentity(a, f)
isEq.lhs should === (isEq.rhs)
}
}

test("Kleisli right identity") {
forAll { (a: Int, f: Int => Try[Long]) =>
val isEq = monadLaws.kleisliRightIdentity(a, f)
isEq.lhs should === (isEq.rhs)
}
}

test("map2Eval is lazy") {
var evals = 0
val bomb: Eval[Try[Int]] = Later { evals += 1; Success(1) }
Try[Int](sys.error("boom0")).map2Eval(bomb)(_ + _).value
evals should === (0)
}
}