How to return the correct type from a generic function passed an associated abstract parameter type

I am trying to write “better” (more idiomatic?) Scala code for the following circumstance: I have a set of classes that will be identified using a reference field that refers to a parallel set of classes in the base case, something like the following:

abstract sealed class Ref(value: String) case class ARef(value: String) extends Ref(value) case class BRef(value: String) extends Ref(value) case class CRef(value: String) extends Ref(value) trait Referenced { type refType <: Ref val ref: refType } trait A extends Referenced { type refType = ARef } trait B extends Referenced { type refType = BRef } trait C extends Referenced { type refType = CRef } 

Another class (which is likely to turn into a state monad state type) will contain lists of these types and provide a function for retrieving an object based on its reference. I want this return value to be printed accordingly, i.e. Indicated

 val aRef = ARef("my A ref") 

I want to be able to make a call like:

 val myA: Option[A] = context.get[A](aRef) 

and be sure to return the [A] option, not just the [Referenced] option. My best attempt to achieve this so far looks something like this:

 trait Context { // ... other stuff ... protected val aList: List[A] protected val bList: List[B] protected val cList: List[C] def get[R <: Referenced](ref: R#refType): Option[R] = { val result = ref match { case aRef: ARef => aList.find(_.ref == aRef) case bRef: BRef => bList.find(_.ref == bRef) case cRef: CRef => cList.find(_.ref == cRef) case _ => throw new RuntimeException("Unknown Ref type for retrieval: "+ref) } result.asInstanceOf[Option[R]] } } 

which seems to work correctly, but has this smelly "asInstanceOf" call. I would be interested to see ideas on how this can be done better (and make sure that I have not just missed the obvious simpler solution).

Note that for other reasons, I still decided to use abstract typing rather than parameter types ( trait A extends Referenced[ARef] ), but could change that if the reasons were convincing enough.

+4
source share
2 answers

The mechanism necessary for this without casting is not so heavy in this case ... this is just another example of a functional dependency .

In the future, we rely on the fact that the Ref type is sealed, so that we can simply list the alternatives. The Ref and Reference hierarchies remain unchanged, and we add the Rel relation type to both express the correspondence between the levels between them and make the appropriate choice of the value level,

 trait Rel[Ref, T] { def lookup(as: List[A], bs: List[B], cs: List[C])(ref: Ref) : Option[T] } object Rel { implicit val relA = new Rel[ARef, A] { def lookup(as: List[A], bs: List[B], cs: List[C])(ref: ARef) : Option[A] = as.find(_.ref == ref) } implicit val relB = new Rel[BRef, B] { def lookup(as: List[A], bs: List[B], cs: List[C])(ref: BRef) : Option[B] = bs.find(_.ref == ref) } implicit val relC = new Rel[CRef, C] { def lookup(as: List[A], bs: List[B], cs: List[C])(ref: CRef) : Option[C] = cs.find(_.ref == ref) } } 

Now we can override Context without matching patterns or casts as follows,

 trait Context { // ... other stuff ... protected val aList: List[A] = ??? protected val bList: List[B] = ??? protected val cList: List[C] = ??? def get[R <: Ref, T](ref: R)(implicit rel: Rel[R, T]): Option[T] = rel.lookup(aList, bList, cList)(ref) } 

And we can use this new definition, for example,

 object Test { def typed[T](t: => T) {} // For pedagogic purposes only val context = new Context {} val aRef = ARef("my A ref") val myA = context.get(aRef) typed[Option[A]](myA) // Optional: verify inferred type of myA val bRef = BRef("my B ref") val myB = context.get(bRef) typed[Option[B]](myB) // Optional: verify inferred type of myB val cRef = CRef("my C ref") val myC = context.get(cRef) typed[Option[C]](myC) // Optional: verify inferred type of myC } 

Note that resolving the implicit Rel argument to get computes the type of the corresponding Reference from the type of the Ref argument, so we can avoid using any explicit type of arguments on get sites.

+9
source

I am just going to repeat my (current) “answer” on my question because I thought it would be interesting / instructive to allow readers to vote up or down to create a more direct comparison with the answers of others.

 trait Context { // ... other stuff ... protected val aList: List[A] protected val bList: List[B] protected val cList: List[C] def get[R <: Referenced](ref: R#refType): Option[R] = { val result = ref match { case aRef: ARef => aList.find(_.ref == aRef) case bRef: BRef => bList.find(_.ref == bRef) case cRef: CRef => cList.find(_.ref == cRef) case _ => throw new RuntimeException("Unknown Ref type for retrieval: "+ref) } result.asInstanceOf[Option[R]] } } 
0
source

Source: https://habr.com/ru/post/1490219/


All Articles