Class type for related types
Let's say we have the following traits:
trait MyValue
object MyValue {
case class MyBoolean(record: Boolean) extends MyValue
case class MyLong(record: Long) extends MyValue
}
trait MyValueExtractor[T] {
def apply(record: T): Option[MyValue]
}
trait MyThing[T] {
def name: String
def myValueExtractor: MyValueExtractor[T]
def myValue(record: T): Option[MyValue] = myValueExtractor(record)
}
I want something like this , but without a second type parameter.
Note. I can't really update the tag MyThing
; I am just using this as an illustration of the intended functionality.
trait MyThing[T, U] {
def name: String
def myValueExtractor: MyValueExtractor[T]
def myValue(record: T): Option[MyValue] = myValueExtractor(record)
def myRelatedValue(record: T): Option[U]
}
I am wondering if I can use the type type template to help solve this problem (for example, import some rich class that implicitly gives me a method myRelatedValue
)?
Here rub. Every time T
(above) MyValue.MyBoolean
, U
should be String
. Every time T
is equal MyValue.MyLong
, U
it should be Double
. In other words, there is some underlying mapping between T
and U
.
Is there a good way to do this with a type class?
source to share
Sure. You just need to define some class Mapping
with implementations for your desired type pairs. Then MyThing
could have a method that takes an implicit instance of typeclass and just calls its method.
Here's the code (I removed unnecessary details)
// types
case class MyBoolean(record: Boolean)
case class MyLong(record: Long)
// trait which uses the Mapping typeclass
trait MyThing[T] {
def myRelatedValue[U](record: T)(implicit ev: Mapping[T, U]): Option[U] = ev.relatedValue(record)
}
// typeclass itself
trait Mapping[T, U] {
def relatedValue(record: T): Option[U]
}
object Mapping {
implicit val boolStringMapping = new Mapping[MyBoolean, String] {
def relatedValue(record: MyBoolean) = Some(record.record.toString)
}
implicit val longDoubleMapping = new Mapping[MyLong, Double] {
def relatedValue(record: MyLong) = Some(record.record)
}
}
// usage
val myBoolThing = new MyThing[MyBoolean] {}
val myLongThing = new MyThing[MyLong] {}
val myStringThing = new MyThing[String] {}
myBoolThing.myRelatedValue(MyBoolean(true)) // Some(true)
myLongThing.myRelatedValue(MyLong(42L)) // Some(42.0)
myStringThing.myRelatedValue("someString") // error: could not find implicit value
Note that for example myBoolThing.myRelatedValue(MyBoolean(true))
will give a type Option[U]
. However, since the parameter is myRelatedValue
parameterized, you can help the compiler and call it like myBoolThing.myRelatedValue[String](MyBoolean(true))
, in which case you get Option[String]
. If you try something other than String for MyBoolean, you will get an error.
source to share