Provides much needed syntax and missing conversions for Scalaz 7.1.
Add the following to your build.sbt
file:
libraryDependencies += "com.rubiconproject" %% "rubiz" % "0.4.+"
Import all the additional syntax with import rubiz.syntax.all._
. Specific subsets of syntax can be
imported if preferred (eg, import rubiz.syntax.either._
).
import scalaz.Catchable
import scalaz.syntax.catchable._
import scalaz.effect.IO
import scalaz.concurrent.Task
import rubiz.syntax.catchable._
Check the result inside Catchable
to see if it matches your predicate. If it doesn't, the left becomes your provided value.
(IO("username")
.ensure(new Exception("Can't make a user without a name."))(_.nonEmpty)
.unsafePerformIO)
// res0: String = username
attempt
, but it will only catch/map throwables for which the function is defined. If the function
doesn't match for the throwable it will re-throw. A common use case is to map IO (user or DB)
exceptions that you have a better type or message for on the left, rethrowing ones you didn't expect.
(IO(throw new java.sql.SQLException).attemptSome {
case sqlE: java.sql.SQLException => "Computer says no."
}
.unsafePerformIO)
// res1: scalaz.\/[String,Nothing] = -\/(Computer says no.)
Lets you define an exception handler on the Catchable that maintains the same type.
(IO[Int](throw new IllegalArgumentException)
.except(e => IO(0))
.unsafePerformIO)
// res2: Int = 0
Like except
but only executes where the function is defined. Has similar use cases to
attemptSome
, but when you have a default you want to use instead of a Throwable
transformation.
(IO[Int](throw new java.sql.SQLException).exceptSome {
case sqlE: java.sql.SQLException => IO(0)
}
.unsafePerformIO)
// res3: Int = 0
Like finally
, but only runs when there was an exception.
(try {
Task.delay(throw new Exception())
.onException(Task.delay(println("THERE WAS A FIREFIGHT!")))
.run
} catch {
case _: Throwable => println("Or something.")
})
// THERE WAS A FIREFIGHT!
// Or something.
Generalizes finally
for all Catchable
, not just IO
.
(try {
Task.delay(throw new Exception())
.ensuring(Task.delay(println("THERE WAS A FIREFIGHT!")))
.run
} catch {
case _: Throwable => println("Or something.")
})
// THERE WAS A FIREFIGHT!
// Or something.
(Task.delay(0)
.ensuring(Task.delay(println("THERE WAS A FIREFIGHT!")))
.run)
// THERE WAS A FIREFIGHT!
// res6: Int = 0
import scalaz.concurrent.Task
import rubiz.syntax.task._
import scala.concurrent.duration._
Wraps timing information up with the result of the task. If the task failed, there isn't a result to wrap up with, and no timing information will be available. This is most useful for local logging of timing information.
(Task.delay(List("Australia", "Japan"))
.withTiming // Task[(FiniteDuration, List[String])]
.map {
case (timing, result) =>
println(s"${result.length} country names were returned in ${timing.toMillis} ms.")
result
}
.run)
// 2 country names were returned in 1 ms.
// res8: List[String] = List(Australia, Japan)
Useful for side effecting the duration of a task to external services, generally a metrics backend or logging service. This logs the duration regardless of the success of the task.
(Task.delay(List("hello", "world"))
.withSideEffectTiming(timing => println(s"${timing.toMillis} ms run, to the metrics service!")) // Task[List[String]]
.run)
// 4 ms run, to the metrics service!
// res9: List[String] = List(hello, world)
Apply a timeout of time
to t
; if the timeout occurs, the resulting TimeoutException includes a message including label
and time
.
Like scalaz.concurrent.Task.timed
but with a non-null, useful error message in the exception.
(Task.delay(Thread.sleep(100.millis.toMillis))
.labeledTimeout(2.millis, "silly example")
.attemptRun)
// res10: scalaz.\/[Throwable,Unit] = -\/(java.util.concurrent.TimeoutException: The 'silly example' task timed out after 2 milliseconds.)
leftMap
for a Task
. Useful for reporting a different exception than the one actually created by the
failure.
(Task.fail(new Exception("Esoteric nonsense."))
.failMap(_ => new Exception("Contextual description of what happened."))
.attemptRun)
// res11: scalaz.\/[Throwable,Nothing] = -\/(java.lang.Exception: Contextual description of what happened.)
Allows you to handle errors and map the successes to a new value.
(Task.now("Success")
.attemptFold(_ => "Failure")(_ ++ "es")
.run)
// res12: String = Successes
(Task.delay[String](throw new Exception("Explosion"))
.attemptFold(_ => "The explosion was contained.")(_ ++ "es")
.run)
// res13: String = The explosion was contained.
Run your function as a side effect if the task is successful and pass the original return value through. Particularly useful for logging.
(Task.now(true)
.peek(b => b match {
case true => println("Element was found.")
case false => println("Element wasn't found.")
})
.run)
// Element was found.
// res14: Boolean = true
Run your function as a side effect if the task fails and pass the original Throwable through. Particularly useful for logging.
(Task.delay[Boolean](throw new Exception("I can't search this list!"))
.peekFail(_ => println("What is an element, really?"))
.attemptRun)
// What is an element, really?
// res15: scalaz.\/[Throwable,Boolean] = -\/(java.lang.Exception: I can't search this list!)
Ensure that a resource is "closed" when a task completes, regardless of whether it's successful.
A CanClose
instance must be scope to call using
.
If the object to be closed isn't java.io.Closeable
then you'll need to define a CanClose
instance.
class CloseableThing extends java.io.Closeable { def close: Unit = println("Not so fast! I have been closed.") }
// defined class CloseableThing
Task.delay(new CloseableThing).using { closeableThing =>
throw new Exception("All your resources are lost to chaos")
}.attemptRun
// Not so fast! I have been closed.
// res16: scalaz.\/[Throwable,Nothing] = -\/(java.lang.Exception: All your resources are lost to chaos)
import scalaz.\/
import scalaz.syntax.either._
import rubiz.syntax.either._
Turns your \/[Throwable, A]
into a Task[A]
.
Useful when you're trying to compose Tasks
and you want to mix in an Either
.
(List("USA", "Canada")
.right[Throwable] // \/[Throwable, List[String]]
.toTask // Task[List[String]]
.run)
// res17: List[String] = List(USA, Canada)
Allows you to convert an Either
to any Monad
that has an Applicative
and Catchable
instance.
This operates like toTask
but is more generic.
// import scalaz.concurrent.Task
("Some Name"
.right[Throwable] // \/[Throwable, String]
.toM[Task] // Task[String]
.run)
// res19: String = Some Name
// import scalaz.effect.IO
(new Exception("Users do bad things")
.left[String] // \/[Throwable, String]
.toM[IO] // IO[String]
.attempt // IO[\/[Throwable, String]]
.unsafePerformIO)
// res21: scalaz.\/[Throwable,String] = -\/(java.lang.Exception: Users do bad things)
try
is a reserved word, so we've resorted to backticks. If you've got an alternative suggestion,
we'd love to hear it.
import scala.util.Try
import rubiz.syntax.`try`._
If you're using Scalaz you'd probably rather be working with an Either
/\/
/Disjunction
than
a Try
.
val badTry = Try(throw new Exception("No really, users."))
// badTry: scala.util.Try[Nothing] = Failure(java.lang.Exception: No really, users.)
badTry.toDisjunction
// res22: scalaz.\/[Throwable,Nothing] = -\/(java.lang.Exception: No really, users.)
If you're in streams-land and want to go directly from a Try
to a Task
, this sugars you on over
there. Useful when using non-Scalaz libs with Scalaz streams.
val okTry = Try("My examples get worse as time goes on")
// okTry: scala.util.Try[String] = Success(My examples get worse as time goes on)
okTry.toTask.run
// res23: String = My examples get worse as time goes on
Run sbt test
to build the project and exercise the unit test suite.
TravisCI builds on every published commit on every branch.
Rubiz is versioned with semver and released to sonatype.
Open an issue on Github if you feel there should be a release published that hasn't been. You can run sbt tut
to regenerate documentation locally if you modify the docs in the /main/tut
directory.
For those with permission to release:
- Install
gpg
and generate a key. Upload that key to a public key server.- Mac users can
brew install gpg pinentry-mac
to get the tools needed.
- Mac users can
- Create a Sonatype credentials file.
- Run
sbt release
- Push the newly created tags and version bump commits to
rubicon-project/rubiz
.