Skip to content

Instantly share code, notes, and snippets.

@tpolecat
Last active February 1, 2018 14:47
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tpolecat/4c377af75491d22c61840b35ceb25e9a to your computer and use it in GitHub Desktop.
Save tpolecat/4c377af75491d22c61840b35ceb25e9a to your computer and use it in GitHub Desktop.
// Wrapper for a side-effecting computation producing a resource and its close operation.
final case class Resource[A](unsafeOpen: () => (A, () => Unit)) { ra =>
// Running a resource is a side-effect. The closed resource is returned. The yielded value is
// typically pure and closing has no effect, but if you *do* leak a file handle for instance,
// it will have been closed before you see it.
def unsafeRun(): A = {
val (a, ca) = unsafeOpen()
ca()
a
}
// Resource forms a monad with `flatMap` and `pure` below. The implementation ensures that the
// outer resource is closed when the inner computation exits (normally or not).
def flatMap[B](f: A => Resource[B]): Resource[B] =
Resource { () =>
val (a, ca) = ra.unsafeOpen()
try { f(a).unsafeOpen() } finally ca()
}
// Map is defined in terms of flatMap.
def map[B](f: A => B): Resource[B] =
flatMap(a => Resource.pure(f(a)))
}
object Resource {
// Lift a pure value into `Resource`. The close operation has no effect.
def pure[A](a: A): Resource[A] =
Resource(() => (a, () => ()))
// We can construct a `Resource` from any expression that computes a `Closeable`.
def closeable[A <: java.io.Closeable](f: => A): Resource[A] =
Resource { () =>
val a = f
(a, () => a.close())
}
}
object Example {
// A trivial resource that logs what it's doing
def testResource(s: String): Resource[String] =
Resource { () =>
println(s"opening $s")
(s, () => println(s"closing $s"))
}
// Induce a failure
def fail[A]: A = {
println("Failing!")
sys.error("oops")
}
// "use" a resource to compute a value
def use(as: Any*): Int = {
println("Used " + as.mkString(", "))
as.length
}
// Run a resource program and show its result
def test[A](ra: Resource[A]): Unit =
try {
println("---")
val a = ra.unsafeRun()
println(s"SUCCEEDED, answer is $a")
} catch {
case e: Exception => println("FAILED!")
}
// Some examples
def main(args: Array[String]): Unit = {
// trivial
test {
testResource("a")
}
// multiple resources
test {
for {
a <- testResource("a")
b <- testResource("b")
c <- testResource("c")
} yield use(a, b, c)
}
// failure in map
test {
for {
a <- testResource("a")
b <- testResource("b")
c <- testResource("c")
} yield {
use(a, b, c)
fail[Int]
}
}
// failure in flatMap
test {
for {
a <- testResource("a")
b <- testResource("b")
_ = fail[Int]
c <- testResource("c")
} yield use(a, b, c)
}
}
// ---
// opening a
// closing a
// SUCCEEDED, answer is a
// ---
// opening a
// opening b
// opening c
// Used a, b, c
// closing c
// closing b
// closing a
// SUCCEEDED, answer is 3
// ---
// opening a
// opening b
// opening c
// Used a, b, c
// Failing!
// closing c
// closing b
// closing a
// FAILED!
// ---
// opening a
// opening b
// Failing!
// closing b
// closing a
// FAILED!
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment