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()