Skip to content

Instantly share code, notes, and snippets.

@x7c1
Last active June 25, 2016 04:46
Show Gist options
  • Select an option

  • Save x7c1/e854f69d716265cd975d3261a12d1c56 to your computer and use it in GitHub Desktop.

Select an option

Save x7c1/e854f69d716265cd975d3261a12d1c56 to your computer and use it in GitHub Desktop.
Substitutes for Reader[ExecutionContext, Future[Either[L, R]]]
trait Fate[X, +L, +R]{
def map[R2](f: R => R2): Fate[X, L, R2]
def flatMap[L2 >: L, R2](f: R => Fate[X, L2, R2]): Fate[X, L2, R2]
def run[L2 >: L, R2 >: R]: X => (Either[L2, R2] => Unit) => Unit
}
object Fate {
def apply[X, L, R](underlying: X => (Either[L, R] => Unit) => Unit): Fate[X, L, R] = {
new FateImpl(underlying)
}
}
private class FateImpl[X, L, R](
underlying: X => (Either[L, R] => Unit) => Unit) extends Fate[X, L, R]{
override def map[R2](f: R => R2): Fate[X, L, R2] = new FateImpl[X, L, R2](
context => g => underlying(context){
case Right(right) => g(Right(f(right)))
case Left(left) => g(Left(left))
}
)
override def flatMap[L2 >: L, R2](f: R => Fate[X, L2, R2]): Fate[X, L2, R2] = new FateImpl[X, L2, R2](
context => g => underlying(context){
case Right(right) => f(right).run(context)(g)
case Left(left) => g(Left(left))
}
)
override def run[L2 >: L, R2 >: R] = {
underlying
}
}
import org.scalatest.{FlatSpecLike, Matchers}
import x7c1.wheat.modern.kinds.RichFate.ToRichFate
import scala.concurrent.ExecutionContext
import scala.concurrent.duration.DurationInt
class FateTest extends FlatSpecLike with Matchers {
val provide = FutureFate.hold[CustomContext, CustomError]
val context = CustomContext(ExecutionContext.global)
it can "compose by for-yield" in {
val fate = for {
n1 <- provide right 1
n2 <- provide right 2
n3 <- provide right 3
} yield {
n1 + n2 + n3
}
fate.testRun(context){
case Left(error) => fail(error.message)
case Right(n) => n shouldBe 6
}
/*
//cannot compile
fate.run(CustomContextBoo(ExecutionContext.global))
// */
}
it should "stop when exception thrown" in {
val fate = for {
n1 <- provide right 1
n2 <- provide right { throw new Exception("boo") }
n3 <- provide right 3
} yield {
n1 + n2 + n3
}
fate.testRun(context){
case Left(error) =>
error.message shouldBe "boo"
case Right(n) =>
fail(s"invalid response: $n")
}
}
it can "await given msec" in {
val fate = for {
start <- provide right System.currentTimeMillis()
n1 <- provide right 1
_ <- provide await 111.millis
n2 <- provide right 2
_ <- provide await 222.millis
n3 <- provide right 3
} yield {
val elapsed = System.currentTimeMillis() - start
elapsed.toInt -> (n1 + n2 + n3)
}
fate.testRun(context){
case Right((elapsed, sum)) =>
sum shouldBe 6
elapsed should be >= 333
case Left(error) =>
fail(error.message)
}
}
}
import x7c1.wheat.macros.reify.HasConstructor
import x7c1.wheat.modern.chrono.HasTimer
import x7c1.wheat.modern.patch.TimerTask
import scala.concurrent.duration.FiniteDuration
import scala.concurrent.{ExecutionContext, Future}
object FutureFate {
type ErrorLike[X] = HasConstructor[Throwable => X]
type HasContext[X] = X => ExecutionContext
class AppliedHolder[X: HasContext, L: ErrorLike]{
def apply[R](f: => Either[L, R]): Fate[X, L, R] =
Fate { x => g =>
implicit val context = implicitly[HasContext[X]] apply x
Future(f) recover {
case e => Left(implicitly[ErrorLike[L]] newInstance e)
} map g
}
def right[A](f: => A): Fate[X, L, A] = {
apply(Right(f))
}
def await(duration: FiniteDuration)(implicit i: HasTimer[X]): Fate[X, L, Unit] =
Fate { x => g =>
val task = TimerTask {
g(Right({}))
}
i.timer.schedule(task, duration.toMillis)
}
}
def hold[X: HasContext, L: ErrorLike]: AppliedHolder[X, L] = new AppliedHolder
}
import concurrent.duration._
import scala.concurrent.{Await, Future, Promise}
object RichFate {
implicit class ToRichFate[X, L, R](fate: Fate[X, L, R]){
def toFuture(context: X): Future[Either[L, R]] = {
val promise = Promise[Either[L, R]]()
fate.run(context){ either =>
try promise trySuccess either
catch { case e: Throwable => promise tryFailure e }
}
promise.future
}
def testRun(context: X)(f: Either[L, R] => Unit): Unit = {
val either = Await.result(toFuture(context), atMost = 5.seconds)
f(either)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment