I think it’s time to revisit this topic. Scala has for some time had scala.util.Using
for resource management but my main issue with it is that it’s not type-safe when using more than one resource. Here’s an example:
import java.io.{BufferedReader,FileReader}
import scala.util.{Try,Using}
def readFirstLineFromFile(path: String): Try[String] = Using.Manager { use =>
val fr = use(new FileReader(path))
val br = use(new BufferedReader(fr))
br.readLine()
}
If you forget to wrap either or both of the resource acquisitions in the use
function, then you have a resource leak that compiler doesn’t catch. I’d like to propose using a for-comprehension:
def readFirstLineFromFile(path: String): String =
for
fr <- AutoClose(new FileReader(path))
br <- AutoClose(new BufferedReader(fr))
do
br.readLine()
I know that a for-comprehension was tried before and found to have issues especially with the .map
method: 2.13 scala.util.Using: surprising behavior in for-comprehensions, `.map` method is non-compositional · Issue #11225 · scala/bug · GitHub
However this time I’d like to suggest the less ambitious goal of only supporting types that extend java.lang.AutoCloseable
. This also solves the problem shown in the GH issue. Well, to be accurate, it ‘solves’ it by catching it at compile time:
resource <- AutoClose(new Res())
^
<pastie>:32: error: inferred type arguments [(Res, Long)] do not conform to method map's type parameter bounds [R2 <: AutoCloseable]
resource <- AutoClose(new Res())
^
<pastie>:32: error: type mismatch;
found : Res => (Res, Long)
required: Res => R2
Finally, after reading the thread it seems the question was asked, would the functionality suggested here be ‘correct’? And what is ‘correct’ in this context?
I think Scala’s standard library should be rather unopinionated (as much as possible), but specially supporting java.lang.AutoCloseable
as resources which can be auto-closed is a very reasonable step to take. There is already some support for it with Using
but it’s not obvious. For advanced use-cases there are already third-party libraries like Cats Effect and fs2 which offer quite sophisticated effect and resource management. But for the standard library, offering for-comprehension support with AutoClose
would be a quick win and showcase Scala’s power and simplicity compared to Java.
Full implementation and details of the example above:
class AutoClose[R <: AutoCloseable] private (val acquire: () => R) extends AnyVal:
import AutoClose.apply
def foreach[U](f: R => U): U =
val resource = acquire()
try f(resource) finally Option(resource).foreach(_.close())
def flatMap[R2 <: AutoCloseable](f: R => AutoClose[R2]): AutoClose[R2] =
foreach(f)
def map[R2 <: AutoCloseable](f: R => R2): AutoClose[R2] = apply(foreach(f))
object AutoClose:
def apply[R <: AutoCloseable](resource: => R): AutoClose[R] =
new AutoClose(() => resource)
class Res extends AutoCloseable:
private var open = true
override def close() = open = false
def use() = if (!open) throw new Exception("Already closed!")
object App:
def main =
val startTime = System.nanoTime
for
resource <- AutoClose(new Res)
endTime = System.nanoTime
do
println(s"initialization took: ${endTime-startTime} ns")
resource.use()
Working version of the above (no compile error):
object App:
def main =
val startTime = System.nanoTime
for
resource <- AutoClose(new Res)
do
val endTime = System.nanoTime
println(s"initialization took: ${endTime-startTime} ns")
resource.use()