Skip to content

Instantly share code, notes, and snippets.

@yasuabe
Created January 30, 2019 15:43
Show Gist options
  • Save yasuabe/25f648589e91c97e97f29f6e9a47ecb1 to your computer and use it in GitHub Desktop.
Save yasuabe/25f648589e91c97e97f29f6e9a47ecb1 to your computer and use it in GitHub Desktop.
cats effect bracket type class demo
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