Search code examples
scalagenericspolymorphismpattern-matching

Leveraging a generic return type in Scala


So I would like to use a generic return type and be able to use the info of that type within the function. Not sure this is possible but here is what I would like:

  def getStuff[A](a: MyObj, b: String): Option[A] = {
    // do some stuff
    A match {
      case String => Some(a.getString(b))
      case Integer => Some(a.getInt(b))
      ...
      case _ => None
    }
  }

However, as you know, A match is not a possibility. Any ideas on how I could achieve this ?


Solution

  • This is a classic case for using a typeclass:

    trait StuffGetter[T] { // typeclass
      def get(obj: MyObj, s: String): Option[T]
    }  
    
    implicit val stringGetter = new StuffGetter[String] {
       def get(o: MyObj, s: String): Option[String] = ???
    }
    implicit val intGetter = new StuffGetter[Int] {
       def get(o: MyObj, s: String): Option[Int] = ???
    }
    
    def getStuff[A](a: MyObj, b: String)(implicit ev: StuffGetter[A]): Option[A] =
      ev.get(a, b)
    
    val stuff0 = getStuff[String](obj, "Hello")  // calls get on stringGetter
    val stuff1 = getStuff[Int](obj, "World") // call get on intGetter
    val stuff2 = getStuff[Boolean](obj, "!") // Compile-time error
    

    The StuffGetter trait defines the operations that you want to perform on the generic type, and each implicit value of that trait provides the implementation for a specific type. (For a custom type these are typically place in the companion object for the type; the compiler will look there for them)

    When getStuff is called the compiler will look for an implicit instance of StuffGetter with the matching type. This will fail if no such instance exists, otherwise it will be passed in the ev parameter.

    The advantage of this is that the "match" is done at compile time and unsupported types are also detected at compile time.