为什么这个getOrElse语句返回类型ANY?

时间:2016-02-11 02:28:36

标签: json scala optional

我正在尝试按照教程https://www.jamesward.com/2012/02/21/play-framework-2-with-scala-anorm-json-coffeescript-jquery-heroku进行操作,但是自从教程开始以来,play-scala已经发生了变化(就像我找到的每个教程一样)。我正在使用2.4.3这要求我真正了解事情是如何运作的,不一定是坏事。

给我带来麻烦的一件事是getOrElse方法。

这是我的Bar.scala模型

package models

import play.api.db._
import play.api.Play.current
import anorm._
import anorm.SqlParser._

case class Bar(id: Option[Long], name: String)

object Bar {

  val simple = {
    get[Option[Long]]("id") ~
    get[String]("name") map {
      case id~name => Bar(id, name)
    }
  }

  def findAll(): Seq[Bar] = {
    DB.withConnection { implicit connection =>
      SQL("select * from bar").as(Bar.simple *)
    }
  }

  def create(bar: Bar): Unit = {
    DB.withConnection { implicit connection =>
      SQL("insert into bar(name) values ({name})").on(
        'name -> bar.name
      ).executeUpdate()
    }
  }

}

和我的BarFormat.scala Json格式化程序

package models

import play.api.libs.json._
import anorm._

package object Implicits {
  implicit object BarFormat extends Format[Bar] {
    def reads(json: JsValue):JsResult[Bar] = JsSuccess(Bar(
      Option((json \ "id").as[Long]),
      (json \ "name").as[String]
    ))

    def writes(bar: Bar) = JsObject(Seq(
      "id" -> JsNumber(bar.id.getOrElse(0L)),
      "name" -> JsString(bar.name)
    ))

  }
}

为了完整性,我的Application.scala控制器:

package controllers

import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
import javax.inject.Inject
import javax.inject._
import play.api.i18n.{ I18nSupport, MessagesApi, Messages, Lang }
import play.api.libs.json._

import views._
import models.Bar
import models.Implicits._

class Application  @Inject()(val messagesApi: MessagesApi) extends Controller  with I18nSupport {

  val barForm = Form(
    single("name" -> nonEmptyText)
  )

  def index = Action {
    Ok(views.html.index(barForm))
  }

  def addBar() = Action { implicit request =>
    barForm.bindFromRequest.fold(
      errors => BadRequest,
      {
        case (name) =>
          Bar.create(Bar(None, name))
          Redirect(routes.Application.index())
      }
    )
  }

  def listBars() = Action { implicit request =>
    val bars = Bar.findAll()

    val json = Json.toJson(bars)

    Ok(json).as("application/json")

  }

和路线

 # Routes
 # This file defines all application routes (Higher priority routes first)
 # ~~~~

 # Home page
 POST       /addBar                     controllers.Application.addBar
 GET     /                           controllers.Application.index
 GET        /listBars                   controllers.Application.listBars

 # Map static resources from the /public folder to the /assets URL path
 GET     /assets/*file               controllers.Assets.versioned(path="/public", file: Asset)

当我尝试运行我的项目时,我收到以下错误:

Compilation Error

现在bar.id被定义为一个Option [Long]所以bar.id.getOrElse(0L)应该返回一个Long,据我所知,但它显然返回了Any。谁能帮我理解为什么?

谢谢!

2 个答案:

答案 0 :(得分:7)

这就是Scala中类型推断的工作方式......

首先,存在从IntBigDecimal的隐式转换:

scala> (1 : Int) : BigDecimal
res0: BigDecimal = 1

该转换允许在构造选项之前转换Int

scala> Some(1) : Option[BigDecimal]
res1: Option[BigDecimal] = Some(1)

如果我们自己尝试getOrElse类型可以修复,我们会得到预期类型Int

scala> Some(1).getOrElse(2)
res2: Int = 1

然而,这不起作用(你遇到的问题):

scala> Some(1).getOrElse(2) : BigDecimal
<console>:11: error: type mismatch;
 found   : Any
 required: BigDecimal
       Some(1).getOrElse(2) : BigDecimal
                        ^

在执行类型推断之后,Scala的隐式转换最后启动。这是有道理的,因为如果你不知道类型,你怎么知道需要应用什么转换。 Scala可以看到BigDecimal是预期的,但它根据Int的类型有Option结果。因此它尝试扩展类型,找不到与BigDecimal的类型层次结构中的Int匹配的任何内容,并因错误而失败。

这样可行,但因为类型在变量声明中已修复:

scala> val v = Some(1).getOrElse(2)
v: Int = 1

scala> v: BigDecimal
res4: BigDecimal = 1

所以我们需要以某种方式帮助编译器 - 任何类型的注释或显式转换都可以工作。选择你喜欢的任何一个:

scala> (Some(1).getOrElse(2) : Int) : BigDecimal
res5: BigDecimal = 1

scala> Some(1).getOrElse[Int](2) : BigDecimal
res6: BigDecimal = 1

scala> BigDecimal(Some(1).getOrElse(2))
res7: scala.math.BigDecimal = 1

答案 1 :(得分:2)

以下是signature for Option.getOrElse method

getOrElse[B >: A](default: ⇒ B): B

术语B >: A表示类型参数B或抽象类型B引用类型为A的超类型,在本例中为AnyLong

的超类型
val l: Long  = 10
val a: Any = l

因此,我们可以使用getOrElse执行非常类似的操作:

val some: Option[Long] = Some(1)
val value: Any = option.getOrElse("potatos")
val none: Option[Long] = None
val elseValue: Any = none.getOrElse("potatos")

这将我们带到您的场景:getOrElse返回的类型将是Any而不是BigDecimal,因此您需要另一种方法来处理这种情况,例如使用{ {1}},每个实例:

fold

其他一些可以帮助您的讨论:

  1. Why is Some(1).getOrElse(Some(1)) not of type Option[Int]?
  2. Option getOrElse type mismatch error