容器嵌套类型的 Scala 隐式转换
Scala implicit conversion of container nested types
考虑以下示例:
case class A()
case class B()
object Conversions {
implicit def aToB(a: A): B = B()
implicit def convert[U, T](seq: Seq[U])(implicit converter: U => T): Seq[T] = {
seq.map(converter)
}
}
object Main {
import Conversions._
def main(args: Array[String]): Unit = {
val sa = Seq(A())
def example(): Seq[B] = sa
}
}
这个例子不会被版本 2.11.8 的 scala 编译器编译。我使用 IntelliJ Idea 进行编译,但实际上 idea 不会即时产生错误并显示隐式用于转换:
Screenshot from Intellij Idea
为了解决这个问题,我使用了此处描述的方法:
"Scala: Making implicit conversion A->B work for Option[A] -> Option[B]"
我的代码开始如下所示:
case class A()
case class B()
object Conversions {
implicit def aToB(a: A): B = B()
trait ContainerFunctor[Container[_]] {
def map[A, B](container: Container[A], f: A => B): Container[B]
}
implicit object SeqFunctor extends ContainerFunctor[Seq] {
override def map[A, B](container: Seq[A], f: (A) => B): Seq[B] = {
Option(container).map(_.map(f)).getOrElse(Seq.empty[B])
}
}
implicit def functorConvert[F[_], A, B](x: F[A])(implicit f: A => B, functor: ContainerFunctor[F]): F[B] = functor.map(x, f)
}
object Main {
import Conversions._
def main(args: Array[String]): Unit = {
val sa = Seq(A())
def example(): Seq[B] = sa
}
}
这段代码编译得很好,可以按需运行。
我的问题是:
为什么第一种方法编译失败?
这在某种程度上与类型擦除有关吗?如果是,Functor 的使用如何帮助它?
编译器如何为这两种情况解析隐式?
Why the first approach fails to compile?
I've opened a bug for this issue.
这似乎是隐式搜索中的编译器怪癖。由于您提供了转换 Seq[A] => Seq[B]
的 convert
方法,因此编译器无法正确对齐类型。这是使用 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)))
似乎搜索 #3 正在尝试调整 conforms
(<:<
),这需要整个隐式搜索从 A => B
到 A => A
。如果我用 -Yno-predef
编译,隐式转换成功:
| | |-- [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?
第二个示例之所以有效,是因为您现在明确说明了如何使用 Functor
类型 class 将 Seq[A]
映射到 Seq[B]
以及何时编译器看到一个 Seq[A]
,它有一个隐含的将它转换为 Seq[B]
:
def example(): Seq[B] = Conversions.functorConvert[Seq, A, B](sa)({
((a: A) => Conversions.aToB(a))
}, Conversions.SeqFunctor);
请注意,您需要从 A => B
和 Functor[Seq]
进行转换才能映射所有 A
以将它们转换为 B
,这是它使用 conversions.aToB
.
的作用
考虑以下示例:
case class A()
case class B()
object Conversions {
implicit def aToB(a: A): B = B()
implicit def convert[U, T](seq: Seq[U])(implicit converter: U => T): Seq[T] = {
seq.map(converter)
}
}
object Main {
import Conversions._
def main(args: Array[String]): Unit = {
val sa = Seq(A())
def example(): Seq[B] = sa
}
}
这个例子不会被版本 2.11.8 的 scala 编译器编译。我使用 IntelliJ Idea 进行编译,但实际上 idea 不会即时产生错误并显示隐式用于转换:
Screenshot from Intellij Idea
为了解决这个问题,我使用了此处描述的方法:
"Scala: Making implicit conversion A->B work for Option[A] -> Option[B]"
我的代码开始如下所示:
case class A()
case class B()
object Conversions {
implicit def aToB(a: A): B = B()
trait ContainerFunctor[Container[_]] {
def map[A, B](container: Container[A], f: A => B): Container[B]
}
implicit object SeqFunctor extends ContainerFunctor[Seq] {
override def map[A, B](container: Seq[A], f: (A) => B): Seq[B] = {
Option(container).map(_.map(f)).getOrElse(Seq.empty[B])
}
}
implicit def functorConvert[F[_], A, B](x: F[A])(implicit f: A => B, functor: ContainerFunctor[F]): F[B] = functor.map(x, f)
}
object Main {
import Conversions._
def main(args: Array[String]): Unit = {
val sa = Seq(A())
def example(): Seq[B] = sa
}
}
这段代码编译得很好,可以按需运行。
我的问题是:
为什么第一种方法编译失败?
这在某种程度上与类型擦除有关吗?如果是,Functor 的使用如何帮助它?
编译器如何为这两种情况解析隐式?
Why the first approach fails to compile?
I've opened a bug for this issue.
这似乎是隐式搜索中的编译器怪癖。由于您提供了转换 Seq[A] => Seq[B]
的 convert
方法,因此编译器无法正确对齐类型。这是使用 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)))
似乎搜索 #3 正在尝试调整 conforms
(<:<
),这需要整个隐式搜索从 A => B
到 A => A
。如果我用 -Yno-predef
编译,隐式转换成功:
| | |-- [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?
第二个示例之所以有效,是因为您现在明确说明了如何使用 Functor
类型 class 将 Seq[A]
映射到 Seq[B]
以及何时编译器看到一个 Seq[A]
,它有一个隐含的将它转换为 Seq[B]
:
def example(): Seq[B] = Conversions.functorConvert[Seq, A, B](sa)({
((a: A) => Conversions.aToB(a))
}, Conversions.SeqFunctor);
请注意,您需要从 A => B
和 Functor[Seq]
进行转换才能映射所有 A
以将它们转换为 B
,这是它使用 conversions.aToB
.