Skip to content

Commit

Permalink
Merge pull request #592 from ceedubs/fromTryCatch
Browse files Browse the repository at this point in the history
Don't infer Null in fromTryCatch
  • Loading branch information
mpilquist committed Nov 7, 2015
2 parents 2329e47 + 5411e93 commit dac2335
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 23 deletions.
26 changes: 26 additions & 0 deletions core/src/main/scala/cats/NotNull.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package cats

/**
* An instance of `NotNull[A]` indicates that `A` does not have a static type
* of `Null`.
*
* This can be useful in preventing `Null` from being inferred when a type
* parameter is omitted.
*/
sealed trait NotNull[A]

object NotNull {
/**
* Since NotNull is just a marker trait with no functionality, it's safe to
* reuse a single instance of it. This helps prevent unnecessary allocations.
*/
private[this] val singleton: NotNull[Any] = new NotNull[Any] {}

private[this] def ambiguousException: Exception = new Exception("An instance of NotNull[Null] was used. This should never happen. Both ambiguous NotNull[Null] instances should always be in scope if one of them is.")

implicit def `If you are seeing this, you probably need to add an explicit type parameter somewhere, beause Null is being inferred.`: NotNull[Null] = throw ambiguousException

implicit def ambiguousNull2: NotNull[Null] = throw ambiguousException

implicit def notNull[A]: NotNull[A] = singleton.asInstanceOf[NotNull[A]]
}
16 changes: 11 additions & 5 deletions core/src/main/scala/cats/data/Validated.scala
Original file line number Diff line number Diff line change
Expand Up @@ -228,22 +228,28 @@ trait ValidatedFunctions {
* the resulting `Validated`. Uncaught exceptions are propagated.
*
* For example: {{{
* val result: Validated[NumberFormatException, Int] = fromTryCatch[NumberFormatException] { "foo".toInt }
* val result: Validated[NumberFormatException, Int] = catchOnly[NumberFormatException] { "foo".toInt }
* }}}
*/
def fromTryCatch[T >: Null <: Throwable]: FromTryCatchPartiallyApplied[T] = new FromTryCatchPartiallyApplied[T]
def catchOnly[T >: Null <: Throwable]: CatchOnlyPartiallyApplied[T] = new CatchOnlyPartiallyApplied[T]

final class FromTryCatchPartiallyApplied[T] private[ValidatedFunctions] {
def apply[A](f: => A)(implicit T: ClassTag[T]): Validated[T, A] = {
final class CatchOnlyPartiallyApplied[T] private[ValidatedFunctions] {
def apply[A](f: => A)(implicit T: ClassTag[T], NT: NotNull[T]): Validated[T, A] =
try {
valid(f)
} catch {
case t if T.runtimeClass.isInstance(t) =>
invalid(t.asInstanceOf[T])
}
}
}

def catchNonFatal[A](f: => A): Validated[Throwable, A] =
try {
valid(f)
} catch {
case scala.util.control.NonFatal(t) => invalid(t)
}

/**
* Converts a `Try[A]` to a `Validated[Throwable, A]`.
*/
Expand Down
19 changes: 13 additions & 6 deletions core/src/main/scala/cats/data/Xor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -217,20 +217,27 @@ trait XorFunctions {
* the resulting `Xor`. Uncaught exceptions are propagated.
*
* For example: {{{
* val result: NumberFormatException Xor Int = fromTryCatch[NumberFormatException] { "foo".toInt }
* val result: NumberFormatException Xor Int = catching[NumberFormatException] { "foo".toInt }
* }}}
*/
def fromTryCatch[T >: Null <: Throwable]: FromTryCatchPartiallyApplied[T] =
new FromTryCatchPartiallyApplied[T]
def catchOnly[T >: Null <: Throwable]: CatchOnlyPartiallyApplied[T] =
new CatchOnlyPartiallyApplied[T]

final class FromTryCatchPartiallyApplied[T] private[XorFunctions] {
def apply[A](f: => A)(implicit T: ClassTag[T]): T Xor A =
final class CatchOnlyPartiallyApplied[T] private[XorFunctions] {
def apply[A](f: => A)(implicit CT: ClassTag[T], NT: NotNull[T]): T Xor A =
try {
right(f)
} catch {
case t if T.runtimeClass.isInstance(t) =>
case t if CT.runtimeClass.isInstance(t) =>
left(t.asInstanceOf[T])
}
}

def catchNonFatal[A](f: => A): Throwable Xor A =
try {
right(f)
} catch {
case scala.util.control.NonFatal(t) => left(t)
}

/**
Expand Down
4 changes: 2 additions & 2 deletions docs/src/main/tut/traverse.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ import cats.std.list._
import cats.syntax.traverse._
def parseIntXor(s: String): Xor[NumberFormatException, Int] =
Xor.fromTryCatch[NumberFormatException](s.toInt)
Xor.catchOnly[NumberFormatException](s.toInt)
def parseIntValidated(s: String): ValidatedNel[NumberFormatException, Int] =
Validated.fromTryCatch[NumberFormatException](s.toInt).toValidatedNel
Validated.catchOnly[NumberFormatException](s.toInt).toValidatedNel
val x1 = List("1", "2", "3").traverseU(parseIntXor)
val x2 = List("1", "abc", "3").traverseU(parseIntXor)
Expand Down
11 changes: 9 additions & 2 deletions docs/src/main/tut/xor.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,13 +340,20 @@ val xor: Xor[NumberFormatException, Int] =
}
```

However, this can get tedious quickly. `Xor` provides a `fromTryCatch` method on its companion object
However, this can get tedious quickly. `Xor` provides a `catchOnly` method on its companion object
that allows you to pass it a function, along with the type of exception you want to catch, and does the
above for you.

```tut
val xor: Xor[NumberFormatException, Int] =
Xor.fromTryCatch[NumberFormatException]("abc".toInt)
Xor.catchOnly[NumberFormatException]("abc".toInt)
```

If you want to catch all (non-fatal) throwables, you can use `catchNonFatal`.

```tut
val xor: Xor[Throwable, Int] =
Xor.catchNonFatal("abc".toInt)
```

## Additional syntax
Expand Down
13 changes: 9 additions & 4 deletions tests/src/test/scala/cats/tests/ValidatedTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,21 @@ class ValidatedTests extends CatsSuite {
Applicative[Validated[String, ?]].ap2(Invalid("1"), Invalid("2"))(Valid(plus)) should === (Invalid("12"))
}

test("fromTryCatch catches matching exceptions") {
assert(Validated.fromTryCatch[NumberFormatException]{ "foo".toInt }.isInstanceOf[Invalid[NumberFormatException]])
test("catchOnly catches matching exceptions") {
assert(Validated.catchOnly[NumberFormatException]{ "foo".toInt }.isInstanceOf[Invalid[NumberFormatException]])
}

test("fromTryCatch lets non-matching exceptions escape") {
test("catchOnly lets non-matching exceptions escape") {
val _ = intercept[NumberFormatException] {
Validated.fromTryCatch[IndexOutOfBoundsException]{ "foo".toInt }
Validated.catchOnly[IndexOutOfBoundsException]{ "foo".toInt }
}
}

test("catchNonFatal catches non-fatal exceptions") {
assert(Validated.catchNonFatal{ "foo".toInt }.isInvalid)
assert(Validated.catchNonFatal{ throw new Throwable("blargh") }.isInvalid)
}

test("fromTry is invalid for failed try"){
forAll { t: Try[Int] =>
t.isFailure should === (Validated.fromTry(t).isInvalid)
Expand Down
13 changes: 9 additions & 4 deletions tests/src/test/scala/cats/tests/XorTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,21 @@ class XorTests extends CatsSuite {

checkAll("? Xor ?", BifunctorTests[Xor].bifunctor[Int, Int, Int, String, String, String])

test("fromTryCatch catches matching exceptions") {
assert(Xor.fromTryCatch[NumberFormatException]{ "foo".toInt }.isInstanceOf[Xor.Left[NumberFormatException]])
test("catchOnly catches matching exceptions") {
assert(Xor.catchOnly[NumberFormatException]{ "foo".toInt }.isInstanceOf[Xor.Left[NumberFormatException]])
}

test("fromTryCatch lets non-matching exceptions escape") {
test("catchOnly lets non-matching exceptions escape") {
val _ = intercept[NumberFormatException] {
Xor.fromTryCatch[IndexOutOfBoundsException]{ "foo".toInt }
Xor.catchOnly[IndexOutOfBoundsException]{ "foo".toInt }
}
}

test("catchNonFatal catches non-fatal exceptions") {
assert(Xor.catchNonFatal{ "foo".toInt }.isLeft)
assert(Xor.catchNonFatal{ throw new Throwable("blargh") }.isLeft)
}

test("fromTry is left for failed Try") {
forAll { t: Try[Int] =>
t.isFailure should === (Xor.fromTry(t).isLeft)
Expand Down

0 comments on commit dac2335

Please sign in to comment.