One of the classic disadvantages of using typeclasses in Scala is that it can break down when dealing with multiple values who have different typeclass instances. For example, maybe you would like to create a List of things that can be shown (via Show
):
trait Show[-A] {
def show(a: A): String
}
object Show {
implicit val stringShow: Show[String] = identity[String]
implicit val intShow: Show[Int] = _.toString
}
val myShows: List[(things that have a show instance)] = List(1, "hello", 3)
val strings: List[String] = myShows.map(/** instance.show(_) */)
There’s not really a nice way of doing this in Scala out of the box, yet it is how we program using OOP polymorphism all the time, so why not allow this using typeclasses?
I had wondered this a while ago, and forgot about it. But then once I started looking at Rust, I saw that they have this feature, and they call it “trait Objects”
Here’s an example of that and here is the official docs if you’re interested
As for an implementation in Scala one can do something like this:
trait TraitObject[T[_]] {
type Data
def data: Data
def _trait: T[Data]
}
object TraitObject {
def apply[D, T[_]](_data: D)(implicit __trait: T[D]): TraitObject[T] { type Data = D} =
new TraitObject[T] {
type Data = D
val data: Data = _data
val _trait: T[Data] = __trait
}
object Implicits {
object Converters {
implicit class DataToTraitObject[D](data: D) {
def traitObject[T[_]](implicit _trait: T[D]): TraitObject[T] { type Data = D} = apply(data)
}
}
object Conversions {
implicit def dataToTraitObject[T[_], D: T](data: D): TraitObject[T] { type Data = D} = apply(data)
implicit def traitObjectToTypeclass[T[_]](traitObject: TraitObject[T]): T[traitObject.Data] = traitObject._trait
}
}
}
and accomplish the same thing pretty easily, and can expose the following syntax
(with implicit conversions)
val myShows: List[TraitObject[Show]] = List(1, "hello", 2, "world")
val strings: List[String] = myShows.map(to => to._trait.show(to.data)))
(or only converters)
val myShows: List[TraitObject[Show]] = List(
1.traitObject,
"hello".traitObject,
2.traitObject,
"world".traitObject
)
val strings: List[String] = myShows.map(to => to._trait.show(to.data)))
(or no implicits)
val shows1: List[TraitObject[Show]] = List(TraitObject(1), TraitObject("hello"))
// some helpful type aliases
val shows2: List[TObj[Show]] = List(TObj(1), TObj("hello"))
I was also pointed towards this polymorphic library to see prior work (which includes Instance
which accomplishes this as well).
Does it seem like a good or bad idea to promote typeclass based polymorphism in this way, maybe either in the std lib or in the platform?
Link to full example source code
Thanks a bunch!
Edit:
For further context, here is the enumeration of existing concepts in other libraries / languages. Will keep this up to date if I find more.
TraitObject[T[_]]
from Rust
Instance[T[_]]
from Polymorphic
Pack[T[_]]
from Haskell