0

I've got the following helper method in my project:

def close(c: Closeable) {
  Option(c).foreach(s => Try(s.close))
}

I've got some classes that have a close method but do not implement Closeable. If I change the helper method to use structural types I can still use it on these classes:

def close(c: {def close()}) {
  Option(c).foreach(s => Try(s.close))
}

However this introduces use of reflection which is something that I'd like to avoid in runtime.

Is there a way to use something similar to structural typing without inducing runtime reflection?

I.e in the same way Shapeless allows generic access to fields, maybe implicit parameters + macros could be used to access methods in the same way?

eirirlar
  • 814
  • 6
  • 24
  • I'd suggest avoiding structural types in general, but I don't personally think the underlying use of runtime reflection is a problem, since in this case it doesn't involve giving up type safety, etc. – Travis Brown Feb 26 '17 at 16:18

1 Answers1

2

Use traits to implement the typeclass pattern. When I wrote my original solution it was a bit rough round the edges as I assumed a quick search for convert structural bounds to context bounds would pull up better explanations than I could write. That doesn't seem to be the case. Below is a compiling solution.

object Closeables {
  trait MyCloseable[A] {
    def myClose(a: A): Unit
}

  object MyCloseable {
    implicit object MyCanBeClosed extends MyCloseable[CanBeClosed] {
      def myClose(c: CanBeClosed) = c.nonStandardClose()
    }
  }
}

class CanBeClosed {
  def nonStandardClose(): Unit = println("Closing")
}

import Closeables._
object Test extends App {
  def functionThatCloses[A: MyCloseable](a: A) {
    implicitly[MyCloseable[A]].myClose(a)
  }
  def functionThatClosesExplicit[A](a: A)(implicit ev: MyCloseable[A]) {
    ev.myClose(a)
  }
  val c = new CanBeClosed
  functionThatCloses(c)
  functionThatClosesExplicit(c)
  functionThatCloses(c)(MyCloseable.MyCanBeClosed)
  functionThatClosesExplicit(c)(MyCloseable.MyCanBeClosed)
}

For each type of class that can be accepted by functionThatCloses you must define and implicit object in MyCloseables.

The compiler looks at the context bound in functionThatCloses and converts it to a function with the definition of functionThatClosesExplicitly.

The compiler than 'finds' the implicit 'evidence' in the definition from the MyCloseables object and uses that.

A Spoty Spot
  • 746
  • 6
  • 19