是否可以使用类型类来实现特征?

问题描述

我有一种情况想要实现给定的特征(在下面的示例中为CanBeString)。我想选择使用新创建的case类(在下面的示例中为NewImplementation)来实现该特征,或者通过向某些预先存在的类型中添加功能来实现它(仅Int在下面的示例中),使用类型类。以下可能是最好的说明:

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.
}

像这样可能吗?我只是在最近才发现类型类的主题,所以我知道我在这里要的可能是可行的,但这只是不明智的-如果是这样,请打个招呼,让我知道更好的成语。

在此之前和之后,谢谢!

解决方法

例如,您可以有两个testAsFunc的重载版本(OOP样式和typeclass样式)

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
}

或者,如果您更愿意拥有唯一的testAsFunc,则可以为要实现的特征的子类型添加类型类的实例

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
}

请注意,如果c同时具有OOP风格的c.asString和扩展方法c.asString,那么实际上只有第一个被调用。