Functional resource safety management
如果无法正常显示,请先停止浏览器的去广告插件。
相关话题:
#zalando
1. Functional
resource safety
management
Functional resource safety
management with the Bracket
typeclass and Resource
Javier Arrieta
23-07-2019
2. TABLE OF CONTENTS
Zalando
Javier
The problem
The bracket typeclass
The Resource Monad
Coding
References
2
3. WE BRING FASHION TO PEOPLE IN 17 COUNTRIES
2008-2009
2010
2011
2012-2013
2018
3
4. 4
5. Who am I
I am Javier, I work for Zalando as a software engineer.
I have been developing software for 40 years now, doing Scala the last 7 years.
@jarrieta
https://www.linkedin.com/in/jarrieta/
We are hiring: https://grnh.se/4f5514971
5
6. The Problem
How do we ensure we free up resources after usage?
It usually involves a lot of boilerplate code
6
7. The Problem - Java way solution
import java.io.{BufferedReader, File, FileReader}
import scala.jdk.CollectionConverters._
import scala.util.control.NonFatal
object alternatives {
type FileParser = File => Either[Throwable, String]
def javaWay(file: File): Either[Throwable, String] = {
try {
val reader = new FileReader(file)
try {
val buffered = new BufferedReader(reader)
Right(buffered.lines().iterator().asScala.mkString("\n"))
} finally {
reader.close()
}
} catch {
case NonFatal(e) => Left(e)
}
}
}
7
8. The Problem - More Scala way solution
import java.io.{BufferedReader, File, FileReader}
import scala.jdk.CollectionConverters._
import scala.util.Try
import scala.util.control.NonFatal
object alternatives {
type FileParser = File => Either[Throwable, String]
val scalaWay: File => Either[Throwable, String] = { file =>
val tryText = for {
reader <- Try(new FileReader(file))
buffered <- Try(new BufferedReader(reader))
text <- Try(buffered.lines().iterator().asScala.mkString("\n"))
} yield text
tryText.toEither
}
}
8
9. The Problem - Using Source
import java.io.FileInputStream
import scala.io.Source
import scala.util.Try
object alternatives {
type FileParser = File => Either[Throwable, String]
val withSource: File => Either[Throwable, String] = {file =>
Try(Source.fromInputStream(new FileInputStream(file)).getLines().mkString("\n")).toEither
}
}
Only works for Input Streams ¯\_(ツ)_/¯
9
10. The Functional Solution - Bracket Typeclass
/**
* An extension of `MonadError` exposing the `bracket` operation,
* a generalized abstracted pattern of safe resource acquisition and
* release in the face of errors or interruption.
*
* @define acquireParam is an action that "acquires" some expensive
*
resource, that needs to be used and then discarded
*
* @define useParam is the action that uses the newly allocated
*
resource and that will provide the final result
*/
trait Bracket[F[_], E] extends MonadError[F, E] {
/**
* A generalized version of [[bracket]] which uses [[ExitCase]]
* to distinguish between different exit cases when releasing
* the acquired resource.
*
* @param acquire $acquireParam
* @param use $useParam
* @param release is the action that's supposed to release the
*
allocated resource after `use` is done, by observing
*
and acting on its exit condition. Throwing inside
*
this function leads to undefined behavior since it's
*
left to the implementation.
*/
def bracketCase[A, B](acquire: F[A])(use: A => F[B])
(release: (A, ExitCase[E]) => F[Unit]): F[B]
10
11. The Functional Solution - Bracket Typeclass - continued
/**
* Operation meant for specifying tasks with safe resource
* acquisition and release in the face of errors and interruption.
*
* This operation provides the equivalent of `try/catch/finally`
* statements in mainstream imperative languages for resource
* acquisition and release.
*
* @param acquire $acquireParam
* @param use $useParam
* @param release is the action that's supposed to release the
*
allocated resource after `use` is done, regardless of
*
its exit condition. Throwing inside this function
*
is undefined behavior since it's left to the implementation.
*/
def bracket[A, B](acquire: F[A])(use: A => F[B])
(release: A => F[Unit]): F[B] =
bracketCase(acquire)(use)((a, _) => release(a))
}
11
12. The Functional Solution - Usage - The Resource class
/**
* @tparam F the effect type in which the resource is allocated and released
* @tparam A the type of resource
*/
sealed abstract class Resource[F[_], A] {
import Resource.{Allocate, Bind, Suspend}
/**
* Allocates a resource and supplies it to the given function. The
* resource is released as soon as the resulting `F[B]` is
* completed, whether normally or as a raised error.
*
* @param f the function to apply to the allocated resource
* @return the result of applying [F] to
*/
def use[B](f: A => F[B])(implicit F: Bracket[F, Throwable]): F[B] = ???
def flatMap[B](f: A => Resource[F, B]): Resource[F, B] = ???
def map[B](f: A => B)(implicit F: Applicative[F]): Resource[F, B] =
}
12
13. The Functional Solution - The example solution
import java.io.{BufferedReader, File, FileReader}
object alternatives {
type FileParser = File => Either[Throwable, String]
val resourceFileParser: FileParser.FileParser = { file =>
val ioText = for {
reader <- Resource.fromAutoCloseable(IO(new FileReader(file)))
buffered <- Resource.fromAutoCloseable(IO(new BufferedReader(reader)))
text <- Resource.liftF(IO(buffered.lines().iterator().asScala.mkString("\n")))
} yield text
ioText.use(IO.pure).attempt.unsafeRunSync()
}
}
13
14. Live coding demo
14
15. References
Self https://docs.google.com/presentation/d/1DEy_2j9dMoEbwiStHDKd4XUd4GxErlHP0LlDdDSoX64
Example https://github.com/javierarrieta/bracket-scala-meetup
Cats-effect https://typelevel.org/cats-effect
Http4s https://http4s.org/v0.20/
ZIO https://zio.dev/
15