package ch_02
import cats.effect.*
import java.io.{File, FileReader}
import java.util.Scanner
import scala.concurrent.duration.*
import utils.*
final case class Connection(url: String):
def open: IO[Unit] = IO(s"opening connection: $url").inspect.void
def close: IO[Unit] = IO(s"closing connection: $url").inspect.void
object brackets:
// cancellation is not aware of the resource leakage, connection.close is never invoked
def leakyConnection: IO[Unit] =
for
fib <- (Connection("wikipedia.org").open *> IO.sleep(5.seconds)).start
_ <- IO.sleep(1.second) *> fib.cancel
yield ()
// manually registering a callback is prone to error and lacks composition
def safeConnection: IO[Unit] =
for
conn <- IO.pure(Connection("wikipedia.org"))
fib <- (conn.open *> IO.sleep(5.seconds)).onCancel(conn.close).start
_ <- IO.sleep(1.second) *> fib.cancel
yield ()
// resource gets released upon error or cancellation
def bracketConnection: IO[Unit] =
for
fib <- IO.pure(Connection("wikipedia.org")).bracket(_.open *> IO.sleep(5.seconds))(_.close).start
_ <- IO.sleep(1.second) *> fib.cancel
yield ()
// ex. read file line by line every 100 millis
def fileScanner(path: String): IO[Scanner] = IO(new Scanner(new FileReader(new File(path))))
def scannerReader(scanner: Scanner): IO[Unit] =
if scanner.hasNextLine then IO.println(scanner.nextLine()) >> IO.sleep(100.millis) >> scannerReader(scanner)
else IO.unit
def fileReader(path: String): IO[Unit] = fileScanner(path).bracket(scannerReader)(scanner => IO(scanner.close()))
// ex. create a connection from a configuration file
// bracket composition is no different from imperative try-catch statements
def connectionFromConfig(path: String): IO[Unit] = fileScanner(path).bracket { scanner =>
IO(Connection(scanner.nextLine())).bracket { _.open *> IO.sleep(5.seconds) } { _.close }
} { scanner => IO(scanner.close()) }