Created
January 30, 2019 15:43
-
-
Save yasuabe/25f648589e91c97e97f29f6e9a47ecb1 to your computer and use it in GitHub Desktop.
cats effect bracket type class demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package cats_effect_typeclass_exericise.bracket | |
import java.time.format.DateTimeFormatter | |
import cats.MonadError | |
import cats.data.Kleisli | |
import cats.effect.ExitCase.{Canceled, Completed, Error} | |
import cats.effect._ | |
import cats.effect.concurrent.Ref | |
import cats.syntax.either._ | |
import cats.instances.either._ | |
import EitherBracket._ | |
object Util { | |
val formatter = DateTimeFormatter.ofPattern("mm:ss") | |
def putIn(milli: Int, s: Any): String = { | |
Thread.sleep(milli) | |
val threadId = Thread.currentThread.getId | |
val currentMinSec = java.time.LocalTime.now.format(formatter) | |
println(s"in thread($threadId) at $currentMinSec): $s") | |
s.toString | |
} | |
def putLine(s: Any): String = putIn(1000, s) | |
def putIn1(s: Any): String = putIn(1000, s) | |
def putNow(s: Any): String = putIn(0, s) | |
def putRnd(s: Any): String = putIn((math.random * 1000 + 500).toInt, s) | |
} | |
import Util._ | |
trait EitherBracket extends Bracket[ErrorOr, Throwable] { | |
val M: MonadError[ErrorOr, Throwable] | |
def flatMap[A, B](fa: ErrorOr[A])(f: A => ErrorOr[B]) = M.flatMap(fa)(f) | |
def tailRecM[A, B](a: A)(f: A => ErrorOr[Either[A, B]]) = M.tailRecM(a)(f) | |
def raiseError[A](e: Throwable) = M.raiseError(e) | |
def pure[A](x: A) = M.pure(x) | |
def handleErrorWith[A](fa: ErrorOr[A])(f: Throwable => ErrorOr[A]) = M.handleErrorWith(fa)(f) | |
def bracketCase[A, B](acquire: ErrorOr[A]) | |
(use: A => ErrorOr[B]) | |
(release: (A, ExitCase[Throwable]) => ErrorOr[Unit]): ErrorOr[B] = { | |
val errorOrA = acquire | |
val errorOrB = errorOrA flatMap use | |
errorOrA flatMap (a => release(a, Completed)) | |
errorOrB | |
} | |
} | |
object EitherBracket { | |
type ErrorOr[T] = Either[Throwable, T] | |
implicit def eitherBracket: Bracket[ErrorOr, Throwable] = | |
new EitherBracket { val M = catsStdInstancesForEither } | |
} | |
object BracketBracketCaseDemo extends IOApp { | |
def run(args: List[String]): IO[ExitCode] = { | |
var theResource = 0 // mutable variable for didactic purpose | |
def left[R](s: String): ErrorOr[R] = new Exception(s).asLeft[R] | |
def useBracket(implicit E: Bracket[ErrorOr, Throwable]) = E.bracket { | |
// acquire: get resource only when >= two | |
if (theResource < 2) left[Int]("< 2") else theResource.asRight[Throwable] | |
} { n: Int => | |
// use: use resource only when <= 2 | |
if (theResource > 2) left[String]("> 2") else s"result=$n".asRight[Throwable] | |
} { _: Int => | |
// release: reset resource to zero | |
theResource = 0; ().asRight[Throwable] | |
} | |
theResource = 1 | |
val result1 = useBracket // acquire: fail, use: not called, release not called | |
putLine(s"result=$result1, resource=$theResource") // Left("<2"), resource == 1 | |
theResource = 2 | |
val result2 = useBracket // acquire: succeed, use: succeed, release succeed | |
putLine(s"result=$result2, resource=$theResource") // Right(2), resource == 0 | |
theResource = 3 | |
val result3 = useBracket // acquire: succeed, use: fail, release succeed | |
putLine(s"result=$result3, resource=$theResource") // Left(">2"), resource == 0 | |
IO(ExitCode.Success) | |
} | |
} | |
object BracketBracketCaseIODemo extends IOApp { | |
type KleisliIO[T] = Kleisli[IO, (Int, Int), T] | |
def useBracket(implicit B: Bracket[KleisliIO, Throwable]) = { | |
val acquire: KleisliIO[Char] = Kleisli { case (n, _) => | |
IO { val ch = ('a' + n).toChar; putRnd(s"$ch is acquired"); ch } | |
} | |
val use: Char => KleisliIO[String] = ch => Kleisli { case (_, m) => | |
IO { putRnd(s"using $ch"); ch.toString * m } | |
} | |
val release: (Char, ExitCase[Throwable]) => KleisliIO[Unit] = (ch, ec) => Kleisli { n => | |
ec match { | |
case Completed => IO { putRnd(s"completed: releasing $ch") } | |
case Error(th) => IO { putRnd(s"error: ${th.getMessage}") } | |
case Canceled => IO { putRnd(s"canceled: releasing $ch") } | |
} | |
} | |
B.bracketCase(acquire)(use)(release) | |
} | |
def run(args: List[String]): IO[ExitCode] = | |
useBracket.run((2, 5)) map { s => putRnd(s"result=$s"); ExitCode.Success } | |
// ---- example output --- | |
// in thread(11) at 26:16): c is acquired | |
// in thread(11) at 26:17): using c | |
// in thread(11) at 26:18): completed: releasing c | |
// in thread(11) at 26:19): result=ccccc | |
// | |
// Process finished with exit code 0 | |
} | |
object BracketBracketCaseCanceledDemo extends IOApp { | |
import cats.instances.list._ | |
import cats.syntax.applicativeError._ | |
import cats.syntax.functor._ | |
import cats.syntax.parallel._ | |
import scala.concurrent.duration._ | |
type KleisliIO[T] = Kleisli[IO, Ref[IO, Int], T] | |
def runTestBracket(s: String)(ref: Ref[IO, Int]) | |
(implicit B: Bracket[KleisliIO, Throwable]): IO[Unit] = { | |
def acquire: KleisliIO[String] = Kleisli { ref => | |
ref.modify { n => | |
if (n == 0) (n, new Exception(s" $s failed to acquire resource $n").asLeft[Int]) | |
else (n - 1, n.asRight[Throwable]) | |
} flatMap { | |
case Right(n) => | |
putRnd(s"$s acquired resource $n") | |
IO(s"resource#$n") | |
case Left(th) => IO.raiseError(th) | |
} | |
} | |
import cats.syntax.flatMap._ | |
def use(resourceName: String): KleisliIO[Unit] = Kleisli { ref => | |
timer.sleep(100.millis) >> IO { putLine(s"$s is using $resourceName") } >> { ref.modify(n => (n - 1, ())) } | |
} | |
def release(resourceName: String, ec: ExitCase[Throwable]): KleisliIO[Unit] = Kleisli { ref => | |
ec match { | |
case Completed => ref modify (n => (n + 1, putLine(s"$s is releasing $resourceName"))) | |
case Error(th) => IO { putLine(s"$s error in of $resourceName: ${th.getMessage}") } | |
case Canceled => IO { putLine(s"$s's use of $resourceName is canceled") } | |
} | |
} | |
B.bracketCase(acquire)(use)(release).run(ref) | |
} | |
def run(args: List[String]): IO[ExitCode] = { | |
val resultIO = for { | |
ref <- Ref.of[IO, Int](2) | |
a1 = runTestBracket("A")(ref) | |
a2 = runTestBracket("B")(ref) | |
a3 = runTestBracket("C")(ref) | |
_ <- List(a1, a2, a3).parSequence.void | |
} yield () | |
resultIO | |
.recover { case e: Throwable => println(e) } | |
.map{_ => ExitCode.Success } | |
} | |
// this code emits lines like bellow | |
// | |
//------------------------------------------------------------------- | |
// in thread(11) at 36:20): B acquired resource 2 | |
// in thread(12) at 36:20): A acquired resource 1 | |
// in thread(11) at 36:21): B's use of resource#2 is canceled | |
// in thread(12) at 36:21): A is using resource#1 | |
// in thread(11) at 36:22): A's use of resource#1 is canceled | |
// java.lang.Exception: C failed to acquire resource 0 | |
// | |
// Process finished with exit code 0 | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment