fricadelle
fricadelle

Reputation: 487

Scala dynamic return type based on input type

Ok so I don't know what's bugging in this code:

import scala.reflect.runtime.universe._
trait Key extends Product
case class SomeKey(a: Int, b: String) extends Key
case class SomeOtherKey(a: Int, b: String, c:Boolean) extends Key

trait MyTrait[T <: Key] {
  def someField: Int
  def someFunc(implicit tTypeTag: TypeTag[T]): Map[T, Int] = {
    typeOf(tTypeTag) match {
      case t if t =:= typeOf[SomeKey] => Map(SomeKey(1,"2") -> 1)
      case t if t =:= typeOf[SomeOtherKey] => Map(SomeOtherKey(1,"2",true) -> 2)
    }
  }
}

I want (the example has been oversimplified) to be able to return a Map[SomeKey, Int] if someFunc is called from a case class extending MyTrait[SomeKey]. And return a Map[SomeOtherKey, Int] from a MyTrait[SomeOtherKey]

case class MyClass(val s: Int) extends MyTrait[SomeKey] {
  override def someField = s
}

Here a new instance of MyClass should return a Map[SomeKey, Int] when calling someFunc.

But it does not even compile, compiler complaining for each line of the pattern match:

type mismatch;
 found   : (Playground.this.SomeKey, Int)
 required: (T, Int)

or

type mismatch;
 found   : (Playground.this.SomeOtherKey, Int)
 required: (T, Int)

Upvotes: 2

Views: 581

Answers (2)

Mario Galic
Mario Galic

Reputation: 48430

TypeTag will carry over type information to runtime however return-type of a method is compile-time construct, hence the compiler error. Instead consider typeclass solution via extension method (yet again hijacked from Luis' suggestion)

sealed trait Key
final case class SomeKey(a: Int, b: String) extends Key
final case class SomeOtherKey(a: Int, b: String, c: Boolean) extends Key

trait MyTrait[T <: Key]

trait KeyFactory[T <: Key] {
  def someFunc(): Map[T, Int]
}

object KeyFactory {
  def someFunc[T <: Key](implicit ev: KeyFactory[T]) = ev.someFunc
  implicit val someKeyFoo: KeyFactory[SomeKey] = () => Map(SomeKey(1,"2") -> 1)
  implicit val someOtherKey: KeyFactory[SomeOtherKey] = () => Map(SomeOtherKey(1,"2", true) -> 2)
}

implicit final class MyTraitKeyFactory[T <: Key : KeyFactory](private val v: MyTrait[T]) {
  def someFunc(): Map[T, Int] = implicitly[KeyFactory[T]].someFunc()
}

case class MyClass(s: Int) extends MyTrait[SomeKey]
case class MyOtherClass(s: Int) extends MyTrait[SomeOtherKey]


MyOtherClass(42).someFunc()
MyClass(11).someFunc()

which outputs

res0: Map[SomeOtherKey,Int] = Map(SomeOtherKey(1,2,true) -> 2)
res1: Map[SomeKey,Int] = Map(SomeKey(1,2) -> 1)

Upvotes: 3

gandaliter
gandaliter

Reputation: 10111

Here's a solution using type classes and implicits.

trait Key extends Product
case class SomeKey(a: Int, b: String) extends Key
case class SomeOtherKey(a: Int, b: String, c:Boolean) extends Key

trait TypeClass[T] {
  def someFunc: Map[T, Int]
}
object TypeClass {
  implicit def forSomeKey: TypeClass[SomeKey] = new TypeClass[SomeKey] {
    override def someFunc: Map[SomeKey, Int] = Map(SomeKey(1, "2") -> 1)
  }
  implicit def forSomeOtherKey: TypeClass[SomeOtherKey] = new TypeClass[SomeOtherKey] {
    override def someFunc: Map[SomeOtherKey, Int] = Map(SomeOtherKey(1, "2", true) -> 1)
  }
}

trait MyTrait[T <: Key] {
  def someField: Int
  def someFunc(implicit tc: TypeClass[T]): Map[T, Int] = tc.someFunc
}

Upvotes: 4

Related Questions