Search code examples
scalashapelesstype-level-computationframeless

Is there a way to implicitly get instance of an object


We want to create an encoder for arbitrary Enumerations with frameless which
is basically creating a bidirectional mapping from an arbitrary Enumeration to Byte. Currently our less than optimal solution is to give evidence on all of our Enumeration instances so the deserializer can pick it up and call apply on that instance, a method that creates an Enumeration from a Byte. We want to find a way without defining these implicit values, and rather have them automatically picked up from the E type. As far as we know object types are in one-to-one correspondence to a single instance, so we hope there's a mechanism to do this.

For example, the following works

import frameless._

object Color extends Enumeration {
  type Color = Value
  val Red, Green, Blue = Value
}
object State extends Enumeration {
  type State = Value
  val Running, Stopped, Finished = Value
}

implicit val colorEvidence = Color // we want to spare these lines
implicit val stateEvidence = State // we want to spare these lines

implicit def enumToByteInjection[E <: Enumeration](implicit e: E): 
  Injection[E#Value, Byte] = Injection(_.id.toByte, e.apply(_))

Solution

  • Solution 1 (reflection)

    This here compiles and runs when compiled with scalac 2.12.4:

    object Color extends Enumeration {
      type Color = Value
      val Red, Green, Blue = Value
    }
    object State extends Enumeration {
      type State = Value
      val Running, Stopped, Finished = Value
    }
    
    /** Dummy replacement with similar signature */
    class Injection[A, B]()
    
    
    import scala.reflect.runtime.universe.TypeTag
    
    object ItDoesNotWorkInReplObjectsMustBeTopLevel {
    
      implicit def enumToByteInjection[E <: Enumeration](implicit tt: TypeTag[E]): Injection[E#Value, Byte] = {
        val ru = scala.reflect.runtime.universe
        val classLoaderMirror = ru.runtimeMirror(getClass.getClassLoader)
        val moduleSymbol = ru.typeOf[E].termSymbol.asModule
        val moduleMirror = classLoaderMirror.reflectModule(moduleSymbol)
        val companionObject = moduleMirror.instance.asInstanceOf[E]
    
        println(s"/* 1 */ Materialize companion object $companionObject out of nothing!")
                  /* 2 */ ???
                  /* 3 */ // profit!
      }
    
      /** A function that requires implicit `Injection` */
      def testNeedsInjection[E <: Enumeration](implicit inj: Injection[E#Value, Byte]): Unit = 
        println("replace ??? above to continue here")
    
    
      def main(args: Array[String]): Unit = {
        /** Test whether an implicit injection is constructed */
        testNeedsInjection[Color.type] // compiles (crashes, as expected, but compiles)
    
      }
    }
    

    It of course crashes because of the missing implementation at ???, but this comes after the implicit companion object is summoned into existence.

    Gotchas:

    • Doesn't work when run as script
    • Doesn't work in REPL (needs objects to be top-level)
    • Make sure you are using the right ClassLoader, supplying something like classOf[Unit].classLoader leads to NoSuchClassExceptions.
    • Requires TypeTags for the enums (shouldn't be a problem when used with concrete enums on top-level, but could become a problem if the method is supposed to be buried deep in a library but still have "access to the surface": then you would have to pull TypeTags through every method to the surface).

    Solution 2 (implicit objects)

    If you have all enums under your control, then you could simply declare the enumeration objects themselves implicit. The following compiles just nicely, all implicits are inserted as expected:

    implicit object Color extends Enumeration {
      type Color = Value
      val Red, Green, Blue = Value
    }
    implicit object State extends Enumeration {
      type State = Value
      val Running, Stopped, Finished = Value
    }
    
    /** Dummy replacement with similar signature */
    class Injection[A, B]()
    
    implicit def enumToByteInjection[E <: Enumeration](implicit e: E): Injection[E#Value, Byte] = ???
    
    /** A function that requires implicit `Injection` */
    def needsInjection[E <: Enumeration](implicit inj: Injection[E#Value, Byte]): Unit = ???
    
    /** Test whether an implicit injection is constructed */
    needsInjection[Color.type] // compiles (crashes, as expected, but compiles)