Est-il possible d'utiliser une classe de types pour implémenter un trait?

Aug 23 2020

J'ai une situation où je voudrais implémenter un trait donné ( CanBeStringdans l'exemple ci-dessous). Je voudrais avoir la possibilité d'implémenter ce trait en utilisant une classe de cas nouvellement créée ( NewImplementationdans l'exemple ci-dessous), ou de l'implémenter en ajoutant des fonctionnalités à un type préexistant (juste Intdans l'exemple ci-dessous), en utilisant un type classe. Ceci est probablement mieux illustré par ce qui suit:

package example

// typeclass
trait ConvertsToString[A] {
  def asString(value: A): String
}

// the trait I would like the typeclass to implement
trait CanBeString {
  def asString: String
}

// this implementation approach taken from the scala with cats book
object ConvertsToStringInstances {
  implicit val intConvertsToString: ConvertsToString[Int] = 
    new ConvertsToString[Int] {
      def asString(value: Int): String = s"${value}"
    }
}

object ConvertsToStringSyntax {
  implicit class ConvertsToStringOps[A](value: A) {
    def asString(implicit c: ConvertsToString[A]): String = c.asString(value)
  }
}

object Test {
  import ConvertsToStringInstances._
  import ConvertsToStringSyntax._

  def testAsFunc(c: CanBeString): String = c.asString

  case class NewImplementation (f: Double) extends CanBeString {
    def asString = s"{f}"
  }

  println(testAsFunc(NewImplementation(1.002))) // this works fine!
  println(testAsFunc(1)) // this sadly does not.
}

Est-ce que quelque chose comme ça est possible? Je ne découvre que récemment le sujet des classes de types, donc je suis conscient que ce que je demande ici est peut-être possible mais tout simplement imprudent - si c'est le cas, veuillez m'interrompre et me faire savoir ce que pourrait être un meilleur idiome.

Merci d'avance, et aussi après!

Réponses

4 DmytroMitin Aug 23 2020 at 16:50

Par exemple, vous pouvez avoir deux versions surchargées de testAsFunc(style POO et classe de type)

object Test {
  ...

  def testAsFunc(c: CanBeString): String = c.asString
  def testAsFunc[C: ConvertsToString](c: C): String = c.asString

  println(testAsFunc(NewImplementation(1.002))) // {f}
  println(testAsFunc(1)) // 1
}

Ou si vous préférez avoir le seul, testAsFuncvous pouvez ajouter des instances de la classe de type pour les sous-types du trait à implémenter

object ConvertsToStringInstances {
  implicit val intConvertsToString: ConvertsToString[Int] = ...

  implicit def canBeStringSubtypeConvertsToString[A <: CanBeString]: ConvertsToString[A] =
    new ConvertsToString[A] {
      override def asString(value: A): String = value.asString
    }
}

object Test {
  ...

  def testAsFunc[C: ConvertsToString](c: C): String = c.asString

  println(testAsFunc(NewImplementation(1.002))) // {f}
  println(testAsFunc(1)) // 1
}

Veuillez noter que si pour a cil y a à la fois OOP-ish c.asStringet extension-method, c.asStringseule la première est réellement appelée.