简体   繁体   中英

Value class for type class with multiple type parameters

It is a common practice to provide a helper value class for accessing type classes - something like

object Show {
  def apply[A](implicit sh: Show[A]): Show[A] = sh

  def show[A: Show](a: A) = Show[A].show(a)

  implicit class ShowOps[A: Show](a: A) {
    def show = Show[A].show(a)
  }

  implicit val intCanShow: Show[Int] =
    new Show[Int] {
      def show(int: Int): String = s"int $int"
    }
}

In my case, I have a type class with 2 parameters. And I cannot get the value class to work (MapperOps). I keep getting "could not find implicit value for parameter mapper"

trait Mapper[T, D] {
  def toDto(i: T): D
}

object Mapper {
  def map[T, D](i: T)(implicit mapper: Mapper[T, D]): D = implicitly[Mapper[T, D]].toDto(i)

  def apply[T, D](implicit mapper: Mapper[T, D]): Mapper[T, D] = mapper

  implicit def optionMapper[T, D](implicit mapper: Mapper[T, D]): Mapper[Option[T], Option[D]] = {
    new Mapper[Option[T], Option[D]] {
      override def toDto(i: Option[T]): Option[D] = i.map(mapper.toDto)
    }
  }

  implicit def seqMapper[T, D](implicit mapper: Mapper[T, D]): Mapper[Seq[T], Seq[D]] = {
    new Mapper[Seq[T], Seq[D]] {
      override def toDto(i: Seq[T]): Seq[D] = i.map(mapper.toDto)
    }
  }
   // Neither works
  implicit class MapperOps1[T,D](a: T) {
     def toDto = Mapper[T,D].toDto(a)
   }
   implicit class MapperOps2[T,D](a: T) {
     def toDto(implicit mapper: Mapper[T,D]) = Mapper[T,D].toDto(a)
   }
   implicit class MapperOps3[T,D](a: T) {
     def toDto[D](implicit mapper: Mapper[T,D]): D Mapper[T,D].toDto(a)
   }
}

Notice the important difference that in

implicit class ShowOps[A: Show](a: A) { ... }

you have this A : Show part, which essentially desugares into

implicit class ShowOps[A](a: A)(implicit s: Show[A]) { ... }

Since there is no neat [T : MyTypeClass] -syntax for two-parameter "Type-Pair-Classes", you have to provide the implicit Mapper as a separate parameter to the constructor:

implicit class MapperOps1[T, D](a: T)(implicit m: Mapper[T, D]) {
  def toDto = Mapper[T, D].toDto(a)
}

The following little test compiles and outputs "42":

implicit object IntToStringMapper extends Mapper[Int, String] {
  def toDto(i: Int): String = i.toString
}

import Mapper._

val s: String = 42.toDto
println(s)

Alternatively, you could try it with an implicit conversion (compiler will whine at you if you don't enable this feature explicitly, and users will whine at you if you don't use this feature wisely):

class MapperOps1[T,D](a: T, mapperTD: Mapper[T, D]) {
  def toDto = {
    implicit val m: Mapper[T, D] = mapperTD
    Mapper[T,D].toDto(a)
  }
}
import scala.language.implicitConversions
implicit def wrapIntoMapperOps1[T, D]
  (a: T)
  (implicit m: Mapper[T, D]): MapperOps1[T, D] = new MapperOps1(a, m)

I won't comment on your two other attempts: apparently, the compiler cannot instantiate those, because it doesn't get enough information about the type parameters before it has to instantiate a wrapper.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM