Why the first approach fails to compile?
I've opened a bug for this issue.
This seems like a compiler quirk in implicit search. Since you're providing the convert
method which converts Seq[A] => Seq[B]
, the compiler isn't able to properly align the types. This is the output of compiling with Ytyper-debug
:
| [search #3] start `[U, T](seq: Seq[U])(implicit converter: U => T)Seq[T]` inferring type T, searching for adaptation to pt=A => T (silent: method example in Test) implicits disabled
| [search #3] considering aToB
| |-- { ((a: A) => Conversions.aToB(a)) } : pt=A => ? EXPRmode (silent: method example in Test) implicits disabled
| | |-- ((a: A) => Conversions.aToB(a)) : pt=A => ? EXPRmode (silent: method example in Test) implicits disabled
| | | |-- Conversions.aToB(a) EXPRmode (silent: value $anonfun in Test) implicits disabled
| | | | |-- Conversions.aToB BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value $anonfun in Test) implicits disabled
| | | | | \-> (a: A)B
| | | | |-- a : pt=A BYVALmode-EXPRmode (silent: value $anonfun in Test) implicits disabled
| | | | | \-> A
| | | | \-> B
| | | \-> A => B
| | \-> A => B
| [adapt] aToB adapted to { ((a: A) => Conversions.aToB(a)) } based on pt A => T
| [search #3] solve tvars=?T, tvars.constr= >: B
| solving for (T: ?T)
| [search #3] success inferred value of type A => =?B is SearchResult({
| ((a: A) => Conversions.aToB(a))
| }, TreeTypeSubstituter(List(type T),List(B)))
| solving for (A: ?A)
| solving for (A: ?A)
| solving for (A: ?A)
| solving for (A: ?A)
| [search #3] considering $conforms
| solving for (A: ?A)
| [adapt] $conforms adapted to [A]=> <:<[A,A] based on pt A => T
| [search #3] solve tvars=?T, tvars.constr= >: A
| solving for (T: ?T)
| [search #3] success inferred value of type A => =?A is SearchResult(scala.Predef.$conforms[A], TreeTypeSubstituter(List(type T),List(A)))
It seems like the search #3 is trying to adapt conforms
(<:<
) which takes the entire implicit search from A => B
to A => A
. If I compile with the -Yno-predef
, the implicit conversion succeeds:
| | |-- [U, T](seq: Seq[U])(implicit converter: U => T)Seq[T] : pt=Seq[B] EXPRmode (silent: method example in Test) implicits disabled
| | | [search #4] start `[U, T](seq: Seq[U])(implicit converter: U => T)Seq[T]`, searching for adaptation to pt=A => B (silent: method example in Test) implicits disabled
| | | [search #4] considering aToB
| | | |-- { ((a: A) => Conversions.aToB(a)) } : pt=A => B EXPRmode (silent: method example in Test) implicits disabled
| | | | |-- ((a: A) => Conversions.aToB(a)) : pt=A => B EXPRmode (silent: method example in Test) implicits disabled
| | | | | |-- Conversions.aToB(a) : pt=B EXPRmode (silent: value $anonfun in Test) implicits disabled
| | | | | | |-- Conversions.aToB BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value $anonfun in Test) implicits disabled
| | | | | | | \-> (a: A)B
| | | | | | |-- a : pt=A BYVALmode-EXPRmode (silent: value $anonfun in Test) implicits disabled
| | | | | | | \-> A
| | | | | | \-> B
| | | | | \-> A => B
| | | | \-> A => B
| | | [adapt] aToB adapted to { ((a: A) => Conversions.aToB(a)) } based on pt A => B
| | | [search #4] success inferred value of type A => B is SearchResult({
| | | ((a: A) => Conversions.aToB(a))
| | | }, )
| | | |-- [U, T](seq: Seq[U])(implicit converter: U => T)Seq[T] : pt=Seq[B] EXPRmode (silent: method example in Test) implicits disabled
| | | | \-> Seq[B]
| | | [adapt] [U, T](seq: Seq[U])(implicit converter: U => T)Seq[T] adapted to [U, T](seq: Seq[U])(implicit converter: U => T)Seq[T] based on pt Seq[B]
| | | \-> Seq[B]
| | [adapt] Seq[A] adapted to [U, T](seq: Seq[U])(implicit converter: U => T)Seq[T] based on pt Seq[B]
| | \-> Seq[B]
| \-> [def example] ()Seq[B]
Is this somehow related to type erasure and if yes, how usage of
Functor helps with it?
The second example works because you're now laying out explicitly how to map a Seq[A]
into a Seq[B]
by using the Functor
type class and thus when the compiler sees a Seq[A]
, it has an implicit to convert it to Seq[B]
:
def example(): Seq[B] = Conversions.functorConvert[Seq, A, B](sa)({
((a: A) => Conversions.aToB(a))
}, Conversions.SeqFunctor);
Note you require both a conversion from A => B
, and a Functor[Seq]
to be able to map over all A
s to convert them to B
s, which is what it does using conversions.aToB
.