Home > Software design >  Group TypeClass instances by type parameter
Group TypeClass instances by type parameter

Time:10-27

Suppose, I have 2 typeclasses:

trait Foo[A] {
  ...
}

trait Bar[A] {
  ...
}

and then I collect their instances into collections:

val seqFoo : Set[Foo[_]] = ...
val seqBar : Set[Bar[_]] = ...

is there a way to combine this seqs into something like this:

val fooBar = (Foo[Int],Bar[Int]), ... , (Foo[String],Bar[String])

Maybe pattern matching function or type tags is an answer, but I believe this case should have more straightforward solution

CodePudding user response:

For example you can make your type classes abstract classes rather than traits (then they can have parameters), add implicit TypeTag parameters, and make groupBy for your collections.

import scala.reflect.runtime.universe.{TypeTag, Type}

// type class
abstract class Foo[A](implicit val ttag: TypeTag[A]) {
  def foo(): String // dummy method just to distinguish instances
}

// instances
object Foo1 {
  implicit val intFoo: Foo[Int] = new Foo[Int]() {
    override def foo(): String = "Foo1.intFoo"
  }
  implicit val strFoo: Foo[String] = new Foo[String]() {
    override def foo(): String = "Foo1.strFoo"
  }
}
object Foo2 {
  implicit val intFoo: Foo[Int] = new Foo[Int]() {
    override def foo(): String = "Foo2.intFoo"
  }
  implicit val boolFoo: Foo[Boolean] = new Foo[Boolean]() {
    override def foo(): String = "Foo2.boolFoo"
  }
}
object Foo3 {
  implicit val intFoo: Foo[Int] = new Foo[Int]() {
    override def foo(): String = "Foo3.intFoo"
  }
  implicit val strFoo: Foo[String] = new Foo[String]() {
    override def foo(): String = "Foo3.strFoo"
  }
  implicit val boolFoo: Foo[Boolean] = new Foo[Boolean]() {
    override def foo(): String = "Foo3.boolFoo"
  }
}

val seqFoo : Set[Foo[_]] = Set(Foo1.intFoo, Foo1.strFoo, Foo2.intFoo, Foo2.boolFoo, Foo3.intFoo, Foo3.strFoo, Foo3.boolFoo)

val grouped: Map[Type, Set[Foo[_]]] = seqFoo.groupBy(_.ttag.tpe)

// checking that instances are grouped by type
grouped.view.mapValues(_.map(_.foo())).foreach(println)
//(Int,HashSet(Foo3.intFoo, Foo1.intFoo, Foo2.intFoo))
//(String,HashSet(Foo1.strFoo, Foo3.strFoo))
//(Boolean,HashSet(Foo2.boolFoo, Foo3.boolFoo))

Simlarly you can do groupBy for Bar and then do whatever you want with Map[Type, Set[Foo[_]]] and Map[Type, Set[Bar[_]]]. For example merge the Maps

val grouped: Map[Type, Set[Foo[_]]] = ???
val grouped1: Map[Type, Set[Bar[_]]] = ???
val merged: Map[Type, (Set[Foo[_]], Set[Bar[_]])] =
  (grouped.keySet union grouped1.keySet)
    .map(tpe => tpe -> (grouped(tpe), grouped1(tpe)))
    .toMap

I'll copy from my comment. Instances of type classes are intended to be resolved automatically (via implicit resolution) by compiler at compile time. Collecting the instances manually seems weird, so does collecting them at runtime.

My typeclasses instances implement decoder/encoder logic. Also they contain some meta information (name particularly). So i would like to check name-to-type accordance and generate unique names, based on how many instances have same name but different type parameter.

Although names of implicits can sometimes make impact on implicit resolution (1 2 3 4), normally you shouldn't be interested much in names of implicits, nor in name-to-type correspondence. Maybe you should provide some code snippet so that we can understand your setting and goals. Maybe what you need can be done at compile time and/or automatically.


Alternatively, if you prefer to keep type classes traits or you can't modify their code, you can use magnet pattern (1 2 3 4 5 6 7 8)

trait Foo[A] {
  def foo(): String
}

implicit class Magnet[F[_], A: TypeTag](val inst: F[A]) {
  def tpe: Type = typeOf[A]
}

val seqFoo = Set[Magnet[Foo, _]](Foo1.intFoo, Foo1.strFoo, Foo2.intFoo, Foo2.boolFoo, Foo3.intFoo, Foo3.strFoo, Foo3.boolFoo)

val grouped: Map[Type, Set[Magnet[Foo, _]]] = seqFoo.groupBy(_.tpe)

// checking that instances are grouped by type
grouped.view.mapValues(_.map(_.inst.foo())).foreach(println)
//(String,HashSet(Foo1.strFoo, Foo3.strFoo))
//(Int,HashSet(Foo3.intFoo, Foo1.intFoo, Foo2.intFoo))
//(Boolean,HashSet(Foo2.boolFoo, Foo3.boolFoo))

Using the "Prolog in Scala" to find available type class instances

  • Related