Closeable and compatibility from Scala 2.11 to Java 18

Newer versions of Scala have Using, but as far as I know, it isn’t backported, as least not to 2.11. So, I am using a homemade version that until recently depended on this trick

  protected type Closeable = {def close() : Unit}

so that anything that can close() works with the code. However, this apparently involves runtime reflection which triggers a security violation in newer versions of Java (depending on which jar the class comes from). So, I backed off from that version of Closeable to java.io.Closeable or java.lang.AutoCloseable. That works fine except for Scala 2.11 in which Source (and BufferedSource) is not Closeable. To get around that, I’m converting Source with an implicit conversion to something that can (auto)close. To get that, I unfortunately have to use an extra import statement in any file that needs to automatically close a Source. I’d like to not do that.

Does anyone have a way of adding close() to Scala 2.11 Source that can work more transparently? Here’s an outline of some of the code:

object Closer {
  def autoClose3[Resource, Result](resource: Resource)(closer: () => Unit)(function: Resource => Result): Result = ???
  def autoClose[Resource <: AutoCloseable, Result](resource: Resource)(function: Resource => Result): Result = ??? // call to autoClose3

  implicit class AutoCloser[Resource <: AutoCloseable](resource: Resource) {

    def autoClose[Result](function: Resource => Result): Result =
        Closer.autoClose(resource)(function)
  }

  // In Scala 2.11, Source does not inherit from Closeable, so one has to tell Closer how to close() it.
  // This requires the extra import which I would like to avoid.
  implicit class AutoSource[Resource <: Source](resource: Resource) {

    def autoClose[Result](function: Resource => Result) =
        Closer.autoClose3(resource)(() => resource.close())(function)
  }

FWIW scala-collection-compat contains a port of Using for Scala 2.11. Though you would probably still have to implement a Releasable[Source] instance yourself since it’s not AutoCloseable in 2.11.

Either way a typeclass approach may work better for you. Especially if you have control over its implicit scope, so your instances are in scope without extra imports.

object Closer {
  def autoClose3[Resource, Result](resource: Resource)(closer: () => Unit)(function: Resource => Result): Result = ???
  def autoClose[Resource: Releasable, Result](resource: Resource)(function: Resource => Result): Result =
    autoClose3(resource)(() => implicitly[Releasable[Resource]].release(resource))(function)

  trait Releasable[-R] {
    def release(resource: R): Unit
  }
  object Releasable {
    implicit object AutoCloseableIsReleasable extends Releasable[AutoCloseable] {
      def release(resource: AutoCloseable): Unit = resource.close()
    }
    implicit object SourceIsReleasable extends Releasable[Source] {
      def release(resource: Source): Unit = resource.close()
    }
  }

  implicit class AutoCloser[Resource: Releasable](resource: Resource) {

    def autoClose[Result](function: Resource => Result): Result =
        Closer.autoClose(resource)(function)
  }
}
import Closer.AutoCloser
Source.fromString("foo\nbar\n").autoClose(_.getLines.toList)

That’s brilliant and oh so very close! One extra indirection in the right hands can do wonders. Unfortunately, the example

Source.fromString("foo\nbar\n").autoClose(_.getLines.toList)

does not compile on Scala 2 with the “-” in

trait Releasable[-R] {

It complains “value autoClose is not a member of …”. One unsatisfactory way around that is to remove the -. Then all the specific classes need to be listed and maintained as implicit objects under Releasable. There are almost 20. I don’t think that will work even if it did nicely do away with all the extra imports in the client code.

I hope that some additional piece of magic can get around the problem.
.

That’s strange. It seemed to work when I tried it. You could try to make Releasable invariant without the -, and redefine AutoCloseableIsReleasable:

implicit def AutoCloseableIsReleasable[A <: AutoCloseable]: Releasable[A] = 
  new Releasable[A] {
    def release(resource: A): Unit = resource.close()
  }

That’s beautiful! Your guru status is well deserved. It’s working great so far on Scala 2.11 and 2.12 as well as with Java 8 and Java 18, and it achieves the self-containment that I was hoping for.

Ok I see now. It was working on Scala 2.11 but not on higher versions, because then those two implicits are ambiguous. Because Source is also AutoCloseable. You solve that by assigning lower priority to one of those implicits.

trait LowerPriority {
  implicit object AutoCloseableIsReleasable extends Releasable[AutoCloseable] {
    def release(resource: AutoCloseable): Unit = resource.close()
  }
}
object Releasable extends LowerPriority {
  implicit object SourceIsReleasable extends Releasable[Source] {
    def release(resource: Source): Unit = resource.close()
  }
}

Probably a def with a type parameter automatically has lower priority and that’s why the other solution also worked.

Thanks for double checking. I wouldn’t have been able to draw that conclusion from the error message. It’s good to know that it’s behaving the same on different computers.