Closing (Auto)Closeables that exist only in `Either`

176 views Asked by At

I currently face the problem of correctly closing resources that never leave their containing Either.

The relevant code looks something like this:

object SomeError
class MyRes : AutoCloseable { [...] }

fun createRes(): Either<SomeError, MyRes> { [...] }

fun extractData(res: MyRes): String { [...] }

fun theProblem(): Either<SomeError, String> {
    return createRes()
        .map { extractData(it) }
}

What is the most idiomatic way of closing the created MyRes? Closing it before that map prevents extractData from accessing it, and after the map I can't access it anymore via Either's operations. Closing it in extractData severely limits composability.

Currently I have an external List<AutoCloseable> that I iterate over after all the computations, but that can't be the intended way.

I am open to using Arrow Fx (e.g. Resource) if that helps, but I haven't found anything on how to combine Either and Resource in an elegant way.

2

There are 2 answers

3
nomisRev On

It's possible to combine the either and Resource safely.

object SomeError
class MyRes : AutoCloseable { [...] }

fun createRes(): Resource<Either<SomeError, MyRes>> { [...] }
fun extractData(res: MyRes): String { [...] }

suspend fun solution(): Either<SomeError, String> = either {
  createRes().use { either: Either<SomeError, MyRes> ->
    val res = either.bind()
    val string = extractData(res)
    // call other Either code + `bind()` safely here
    [...]

  } // <-- MyRes will automatically close here
}

If in this code you encounter Either.Left and you call bind() on it the Resource will first close, because we jump outside of use, and then either will return the encountered Either.Left.

2
Kolja On

One possible solution I found was wrapping the block passed to map:

fun <B : AutoCloseable, C> andClose(f: (B) -> C): (B) -> C =
    { b: B -> b.use { f(b) } }


fun theProblemSlightlySolved(): Either<SomeError, String> {
    return createRes()
        .map(andClose { extractData(it) })
}