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?

+3


source to share


1 answer


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.

+2


source







All Articles