タイプクラスを使用してトレイトを実装することは可能ですか?

Aug 23 2020

特定のトレイトを実装したい状況があります(CanBeString以下の例)。新しく作成されたケースクラス(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.
}

このようなことは可能ですか?私は最近型クラスのトピックを発見したばかりなので、ここで求めていることは可能かもしれないが、賢明ではないことを認識しています-もしそうなら、チャイムを鳴らして、より良いイディオムが何であるかを知らせてください。

よろしくお願いします!

回答

4 DmytroMitin Aug 23 2020 at 16:50

たとえば、testAsFunc(OOPスタイルとtypeclassスタイル)の2つのオーバーロードバージョンを持つことができます

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
}

cOOP風c.asStringとextension-methodの両方がある場合、c.asString実際には最初のメソッドのみが呼び出されることに注意してください。