Scala推理:1次评估失败,中间值成功

时间:2013-08-04 18:10:22

标签: scala type-inference

我是scala的初学者,不明白这里发生了什么:

鉴于:

val reverse:Option[MyObject] = ...

myObject.isNaire返回布尔值。

如果我这样做:

val v:Option[Boolean] =  reverse.map(_.isNaire)
val b:Boolean = v.getOrElse(false)

有效。

现在,如果我这样做:

val b:Boolean = reverse.map(_.isNaire).getOrElse(false)

无法使用type mismatch: found Any, required Boolean

进行编译

编辑:感谢Beryllium,通过制作SSCCE,我找到了解释的开始。在第一个示例中,myObject是一个java类,因此isNaire是一个java.lang.Boolean。我认为隐式转换应该使这个透明,所以解释仍然是受欢迎的。

class Test(val naire:java.lang.Boolean)

class Other {
  val testValue = Some(new Test(true))
  def mysteriousCompilationError:Boolean = testValue.map(_.naire).getOrElse(false)
}

注意:ScalaCompiler是2.10.2

2 个答案:

答案 0 :(得分:5)

scala.Predef中,存在从java.lang.Booleanscala.Boolean的隐式转换:

  implicit def Boolean2boolean(x: java.lang.Boolean): Boolean = x.booleanValue

因此,在第一种情况val v:Option[Boolean] = reverse.map(_.isNaire)中,编译器会看到java.lang.Boolean,并在范围内查找隐式方法,将其转换为scala.Boolean,它可以方便地在scala.Predef中找到}。

在第二种情况下,testValue.map(_.naire).getOrElse(false),编译器按此顺序执行操作:

  1. Option[Test] => Option[java.lang.Boolean]
  2. getOrElse[B >: A](default: => B): B其中Ajava.lang.BooleanBAny,因为scala.Boolean不是>: java.lang.Boolean
  3. val b:Boolean,编译器无法找到从Anyscala.Boolean的隐式转换
  4. 解决此问题的唯一方法是在映射操作期间告诉编译器使用scala.Predef的隐式转换从java.lang.Boolean转到scala.Boolean

    def works:Boolean = testValue.map[Boolean](_.naire).getOrElse(false)
    

    这是一个常见问题并经常弹出,因为map后跟getOrElse非常便利。要在没有额外类型的情况下正确修复此问题,请在选项上使用fold(catamorphism):

    def worksToo:Boolean = testValue.fold(false)(_.naire)
    

    通过使用fold,您可以获得一些类型安全性,因为没有转换为常见类型。例如,你不能这样做:

    def failsTypeCheck = testValue.fold("test")(_.naire)
    

    虽然编译器没有问题:

    def passesTypeCheck = testValue.map(_.naire).getOrElse("test")
    

答案 1 :(得分:0)

java.lang.Booleanscala.Boolean不一样。为弥合差距,您必须提供隐式转换可以起作用的位置。

有一些模式可以处理这些类型的Java / Scala互操作性问题:


  • 如果可以从Scala端使用不同的方法,则可以使用隐式值类:
object Container {
  implicit class Test2Scala(val test: Test) extends AnyVal {
    def naireForScala: Boolean = test.naire
  }
}

class Other {
  val testValue = Some(new Test(true))

  import Container._
  def mysteriousCompilationError: Boolean = 
    testValue.map(_.naireForScala).getOrElse(false)
}

这在运行时不需要额外的实例。它只是提供了另一种丰富Java类的方法。


  • 如果您可以派生子类,则可以使用DummyImplicit来保留方法的名称:
class Test2(_naire: Boolean) extends Test(_naire) {
  def naire(implicit di: DummyImplicit): Boolean = _naire
}

class Other {
  val testValue = Some(new Test2(true))
  def mysteriousCompilationError: Boolean = 
    testValue.map(_.naire).getOrElse(false)
}

获取不同的方法签名需要DummyImplicit。这有点棘手,在运行时需要一个额外的实例,但Test2Test(就OOP而言)。


  • 将Java实例包装在Scala实例中:
class TestWrapper(test: Test) {
  def naire: Boolean = test.naire
}

class Other {
  val testValue = Some(new TestWrapper(new Test(true)))

  def mysteriousCompilationError: Boolean = 
    testValue.map(_.naire).getOrElse(false)
}

需要一个额外的实例,您必须添加代理,TestWrapper不是Test,但它很简单。