Ok let me explain some more: *:
is tuple cons. So *:
is like ::
and EmptyTuple
is like Nil
. For instance, the triple type (Int, String, Boolean)
is an abbreviation for Int *: String *: Boolean *: EmptyTuple
. Here’s the complete example for what I had in mind:
trait Frag
case class IntFrag(x: Int) extends Frag
case class StringFrag(x: String) extends Frag
trait Fragable[T]:
def toFrags(x: T): List[Frag]
given Fragable[Int]:
def toFrags(x: Int) = List(IntFrag(x))
given Fragable[String]:
def toFrags(x: String) = List(StringFrag(x))
given [A](using af: Fragable[A]) as Fragable[List[A]]:
def toFrags(xs: List[A]) = xs.flatMap(af.toFrags)
given Fragable[EmptyTuple]:
def toFrags(x: EmptyTuple) = Nil
given [A, B <: Tuple](using af: Fragable[A], bf: Fragable[B]) as Fragable[A *: B]:
def toFrags(x: A *: B) = af.toFrags(x.head) ++ bf.toFrags(x.tail)
def f[T](x: T)(using tf: Fragable[T]) =
println(s"got: ${tf.toFrags(x).mkString(", ")}")
@main def Test =
f(1)
f("abc")
f(List(1, 2, 3))
f(1, "a", List("c", "d"))