【问题标题】:Scala: Implictly convert a listScala:隐式转换列表
【发布时间】:2012-03-08 14:44:35
【问题描述】:

这是我想做的简化示例。注释掉的行无法编译。

class Animal
object Animal { implicit def toElephant(a: Animal) = a.asInstanceOf[Elephant] }
class Elephant extends Animal

object Main {

    def main(args: Array[String]) = {
        val a: List[Animal] = List(new Elephant, new Elephant)
        // val e: List[Elephant] = a
    }
}

在正常情况下,e = a 当然是非法的。但是使用隐式函数,可以认为 scala 会自动转换列表中的每个元素。

有没有一种优雅的方式来获得这种行为?如果是,怎么做?

我想知道的是 scala 是否有一些模糊的角落可以强制执行我希望的行为。我对添加杂乱无章的解决方案不感兴趣。我自己能想到它们。例如,可以这样做:

object Animal {
    implicit def toElephant(a: Animal) = a.asInstanceOf[Elephant]
    implicit def toElephant(a: List[Animal]) = a.asInstanceOf[List[Elephant]] }

上面的代码就可以了。

【问题讨论】:

    标签: scala implicit


    【解决方案1】:

    你可以忘记隐式和映射并简单地使用

    val e = a.asInstanceOf[List[Elephant]]
    

    但是,如果您坚持,您当然也可以隐式进行此转换。从 List[A]List[B] 的隐式并不比隐式 A =​​> B 更笨拙。我不会这样做,但如果我这样做了,我会将它概括为重用,在本地导入以限制隐式范围:

    object Implicits {
      implicit def AListToBList[A, B <: A](lst: List[A]) = lst.asInstanceOf[List[B]]
    }
    

    然后在使用现场

    import Implicits._
    val a: List[Animal] = List(new Elephant, new Elephant)
    val e: List[Elephant] = a
    e.head.doSomethingElephanty
    

    注意导入的范围,如有必要,将内容粘贴在 locally 块中。

    【讨论】:

    • 好的。我要做的是创建一个具有单个元素和数组的隐式转换的通用特征并对其进行扩展。我想我应该将我的问题表述为“如何从单元素隐式转换中获得隐式列表转换的效果”。这并没有完全做到这一点,但在某种意义上甚至更好。
    • @Norswap 将(implicit f: A =&gt; B) 添加到上面的隐式定义中。您希望它如何工作取决于列表中的内容是否实际上是正确的类型。如果是,如您的示例所示,请使用上述内容。如果没有,您必须创建一个新列表并使用map(即lst map f)转换对象。
    【解决方案2】:

    但是对于隐式函数,人们可能会认为 scala 会 自动转换列表中的每个元素。

    为什么?

    让我们尝试一下:

    class X[T : Manifest] {
      override def toString = manifest[T].toString
    }
    
    val x = new X[Animal]
    

    Animal 转换为Elephant 的隐式函数将如何帮助将X[Animal] 转换为X[Elephant]?甚至没有要转换的 Animal 实例!但是让我们在那里放一个实例:

    class X[T : Manifest](t: T) {
      override def toString = manifest[T].toString + t.toString
    }
    

    现在我们有了一个实例,但同样,Scala 将如何将 X[Animal] 转换为 X[Elephant]?它甚至无法访问t,因为它是私有的。那就公开吧:

    class X[T : Manifest](val t: T) {
      override def toString = manifest[T].toString + t.toString
    }
    

    在那里,它是公开的。但是如何转换呢?一种方法是:

    new X[Elephant](Animal toElephant x.t)
    

    但 Scala 怎么会知道这样做呢?谁说那是有效的代码,或者说它就足够了?

    如果存在另一个隐式转换,可以编写一个隐式教学X 如何进行自我转换,并且对于List 可能存在类似的隐式,但隐式转换是危险的。它们的数量越少越好。因此,这样的隐含是不存在的。

    【讨论】:

    • 这是一个故意简化的例子。在实践中,我知道可以完成转换(我事先检查过)。有关更多上下文,请参阅我对 Tomasz Nurkiewicz 的回答)。顺便说一句,请随时评论我在该答案中解释的设计。我知道那里有很多被误导的人,但有时我希望人们能回答所提出的问题,而不是总是问为什么:)
    • @Norswap 我问为什么要让你质疑你可能有的假设。通常情况下,当某人无法解决问题时,是假设不正确。
    • +1 非常好。这是对 monad 的常见误解:Foo[Bar] 并不一定意味着在 Foo 内部有一个 Bar。这只是意味着FooBar 上参数化。
    【解决方案3】:

    map 呢?

    val e = a map {_.asInstanceOf[Elephant]}
    

    甚至更好:

    val e = a collect {case e: Elephant => e}
    

    请注意,asInstanceOf 和超类型到子类型的隐式转换都不是很优雅。

    【讨论】:

    猜你喜欢
    • 2012-04-15
    • 2018-09-16
    • 2011-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多