The title is attempting to describe the following subtyping
implicitly[Map[Int, String] <:< Iterable[(Int, String)]]
Type parameter A
is inferred to (Int, String)
here
def foo[A](cc: Iterable[A]): A = cc.head
lazy val e: (Int, String) = foo(Map.empty[Int, String])
however attempting to achieve similar effect using type parameter bounds the best I can do is explicitly specifying arity of the type constructor like so
def foo[F[x,y] <: Iterable[(x,y)], A, B](cc: F[A, B]): (A, B) = cc.head
lazy val e: (Int, String) = foo(Map.empty[Int, String])
because the following errors
def foo[F[x] <: Iterable[x], A](cc: F[A]) = cc.head
lazy val e: (Int, String) = foo(Map.empty[Int, String])
// type mismatch;
// [error] found : A
// [error] required: (Int, String)
// [error] lazy val e: (Int, String) = foo(Map.empty[Int, String])
// [error] ^
Hence using Iterable
as upper bound it seems we need one signature to handle unary type constructors Seq
and Set
, and a separate signature to handle 2-arity type constructor Map
def foo[F[x] <: Iterable[x], A](cc: F[A]): A // When F is Seq or Set
def foo[F[x,y] <: Iterable[(x,y)], A, B](cc: F[A, B]): (A, B) // When F is Map
Is there a way to have a single signature using type bounds that works for all three? Putting it differently, how could we write, say, an extension method that works across all collections?
I think the issue here is that F
is set to Map
, and kindness is wrong. You would have to have say: I have some type X
, that extends F[A]
, so that when I upcast it, I can use it as F[A]
- which in turn we want to be a subtype of Iterable[A]. If we ask about it this way, it sounds hard.
Which is why I personally would just stay at:
@ def foo[A](x: Iterable[A]): A = x.head
defined function foo
@ foo(List(1 -> "test"))
res24: (Int, String) = (1, "test")
@ foo(Map(1 -> "test"))
res25: (Int, String) = (1, "test")
"Give me any x
that is an instance of Iterable[A]
for A
".
If I had to do some derivation... I would probably also go this way. I think this limitation is the reason CanBuildFrom
works the way it works - providing matching for part of the type is hard, especially in cases like Map, so let's provide a whole type at once as a parameter, to limit the number of inference needed.