-
-
Save tpolecat/4c377af75491d22c61840b35ceb25e9a to your computer and use it in GitHub Desktop.
This file contains 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
// 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