2

I have a situation where I'd like to implement a given trait (CanBeString in the example below). I would like to have the option either to implement that trait using a newly created case class (NewImplementation in the example below), or to implement it by adding functionality to some pre-existing type (just Int in the example below), by using a type class. This is probably best illustrated by the below:

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

Is anything like this possible? I'm only recently discovering the topic of typeclasses so I'm aware that what I'm asking for here may be possible but just unwise - if so please chime in and let me know what a better idiom might be.

Thanks in advance, and also afterwards!

3
  • 1
    If you control the original trait and all the types that already extend it, the best would be to just make it a typeclass from the beginning. Commented Aug 23, 2020 at 15:06
  • @LuisMiguelMejíaSuárez - thanks - I understand what you mean and this is a useful pointer. I have looked at my code again and I'm going to be using the same type parameter for the typeclass throughout my code, so I think the sensible thing is to use a typeclass but then to make a concrete trait from it by passing it this type parameter, and use this throughout the code. Commented Aug 24, 2020 at 0:31
  • 1
    Uhm, well it doesn't sound like a bad design, but it is quite unusual. I would just keep asking for the typeclass all the way down. In this case, it seems that you really have is a magnet pattern rather than a typeclass (well, actually like a mix of both where the magnet transformation is not done implicitly but through the typeclass, which I kind of like). Commented Aug 24, 2020 at 0:53

1 Answer 1

4

For example you can have two overloaded versions of testAsFunc (OOP-style and typeclass-style)

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
}

Or if you prefer to have the only testAsFunc then you can add instances of the type class for subtypes of the trait to be implemented

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
}

Please notice that if for a c there are both OOP-ish c.asString and extension-method c.asString then only the first is actually called.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.