【问题标题】:How can I get implicit conversions to work inside collections?如何获得隐式转换以在集合内工作?
【发布时间】:2016-06-23 06:21:26
【问题描述】:

假设我有一个隐式转换:

implicit def aToB(a: A):B={
...
}

如何让这种隐式转换作用于 List 的元素?

如果我有:

val listOfA: List[A] ...

我有一个函数需要一个 B 的列表,是否可以让 Scala 将所有元素从 A 隐式转换为 B?

如果没有隐式转换,转换可能如下所示:

lisftOfA.map(a => new B(a.someValue, a.anotherValue))

但我希望这会像“魔术”一样发生……这要求太多了。

【问题讨论】:

    标签: scala


    【解决方案1】:

    以下是您可能希望考虑的一些替代方案:

    1.使用视图绑定

    如果可以更改采用 B 列表的函数,这将是最简单的解决方案。修改它以接受可以转换为 Bs 的事物列表。也就是说,

    def yourFn(l: List[B]) = ...
    

    会变成

    def yourFn[X <% B](l: List[X]) = ...
    

    然后,您可以使用 listOfA 调用该函数:

    yourFn(listOfA)
    

    2。介绍一种转换方法

    这类似于 Rogach 的第一个解决方案,只是外部转换是非隐式的:

    def convert[B, A <% B](l: List[A]): List[B] = l map { a => a: B }
    

    然后在你的函数调用点,你会写

    yourFn(convert(listOfA))
    

    与 Rogach 的第二个解决方案一样,这比引入隐式转换更安全。

    3.引入隐式转换

    这相当于 Rogach 的第一个解决方案,但表示法更好一些 (IMO)。

    implicit def convert[B, A <% B](l: List[A]): List[B] = l map { a => a: B }
    

    如果此转换在您的调用站点范围内,您可以使用 listOfA 调用您的函数:

    yourFn(listOfA)
    

    离别的想法

    考虑如何以一般方式解决这个问题很有趣。如果我想定义我的转换方法以便它可以处理任何实现map 方法的类型怎么办?即,

    def convert[B, A <% B, C[_]](c: C[A]): C[B] = c map { a => a: B }
    

    这当然行不通,因为签名中没有任何内容表示C 必须实现map 的约束。据我所知,表达这个约束是相当复杂的,并且不能以一种为实现map 的任何类型提供开箱即用支持的方式来完成。见Type-safe Scala sequence comprehensions

    【讨论】:

    • 感谢伟大的例子和解释。一方面,我想知道如何在地图中“键入演员”,即。 a => a: B. 有这样做的名称吗?
    • 称为类型归属。您将B 类型归属A 类型的对象。这与 cast 不同,后者在 Scala 中使用 asInstanceOf 方法,因为它在编译时进行检查并且永远不会导致运行时异常。
    • 这似乎不是@JacobusR 所要求的。我们希望转换为在map 函数“内部”工作,该函数是listOfA 的函数。您已经介绍了 OP 未提及的 yourFn,但我们需要 listOfA.map((b: B) =&gt; ...)convert(listOfA).map((b: B) =&gt; ...) 一样运行。看来您的解决方案只有在我们定义 def yourFn[T](listOfB: List[B), fn: B =&gt; T): List[T] = listOfB.map(fn) 时才有效。也就是说,如果我们包装 map 只是为了帮助进行类型推断,这不是我们想要做的。
    • @silasdavis 听起来您的问题与 OP 不同。 @JacobusR 有一个 List[A] 和一个接受 List[B] 的函数,他想将其应用于他的 List[A] - 我将此函数命名为 yourFn,但在他的问题中引入它的是 OP。跨度>
    • 这个测试成功了吗?在this question 中,我表明 3.(隐式转换)实际上不起作用。如果可能,请看一下:)
    【解决方案2】:

    以下是一个通用解决方案,如果隐式转换 A=>B 在范围内可用,则启用列表的隐式转换 (List[A] => List[B]):

    scala> class A
    defined class A
    
    scala> class B
    defined class B
    
    scala> implicit def a2b(a:A) = new B
    a2b: (a: A)B
    
    scala> implicit def mapI[A,B](l: List[A])(implicit conv: A => B): List[B] = l.map(conv)
    mapI: [A, B](l: List[A])(implicit conv: (A) => B)List[B]
    
    scala> List(new A): List[B]
    res0: List[B] = List(B@efa0bf4)
    

    这是你需要的吗?

    另外,既然你已经有了隐式转换,你可以写:

    listOfA.map(a2b) // List[B]
    

    这会更冗长一些,但可以让您更明确地控制代码。

    【讨论】:

    • 这实际上不起作用: val list = List(new A) val listB: List[B] = list //这不会编译。检查this question
    【解决方案3】:

    我认为最好的可能是

    implicit def asToBs(as: List[A]): List[B] = as map aToB
    

    【讨论】:

    • 你确定,类型擦除不会有问题吗?
    • @om-nom-nom - 为什么应该有?在类型擦除之前解析隐式。
    • +1 我没有采用这种方法,但这是一个令人耳目一新的简单答案,谢谢。
    • 它不起作用。显然列表协方差混淆了编译器。请检查this question
    【解决方案4】:

    为了完整起见,您可能希望考虑采用更通用的方法,使用 CanBuildFrom

    这个程序:

    import scala.collection.generic.CanBuildFrom
    import scala.language.{higherKinds, implicitConversions}
    
    implicit def implyConvertedTraversable[A, B, C[X] <: Traversable[X]](as: C[A])(implicit conversion: A => B, cbf: CanBuildFrom[C[A], B, C[B]]): C[B] = {
      val builder = cbf(as)
      builder.sizeHint(as)
      builder ++= as.map(conversion)
      builder.result()
    }
    
    implicit def implyString(a: Int): String = a.toString
    
    val intList = List(1, 2, 3)
    val intStream = Stream(1, 2, 3)
    
    val stringList: List[String] = intList
    val stringStream: Stream[String] = intStream
    

    产量:

    intList: List[Int] = List(1, 2, 3)
    intStream: scala.collection.immutable.Stream[Int] = Stream(1, ?)
    
    stringList: List[String] = List(1, 2, 3)
    stringStream: Stream[String] = Stream(1, ?)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-07-28
      • 1970-01-01
      • 1970-01-01
      • 2020-11-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多