Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this getOrElse statement return type ANY?

I am trying to follow the tutorial https://www.jamesward.com/2012/02/21/play-framework-2-with-scala-anorm-json-coffeescript-jquery-heroku but of course play-scala has changed since the tutorial (as seems to be the case with every tutorial I find). I am using 2.4.3 This requires I actually learn how things work, not necessarily a bad thing.

One thing that is giving me trouble is the getOrElse method.

Here is my Bar.scala model

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()
    }
  }

}

and my BarFormat.scala Json formatter

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)
    ))

  }
}

and for completeness my Application.scala controller:

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")

  }

and routes

 # 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)

When I try to run my project I get the following error:

Compilation Error

now bar.id is defined as an Option[Long] so bar.id.getOrElse(0L) should return a Long as far as I can tell, but it is clearly returning an Any. Can anyone help me understand why?

Thank You!

like image 678
vextorspace Avatar asked Feb 11 '16 02:02

vextorspace


People also ask

What does getOrElse do in Scala?

getOrElse – Retrieve the value if the object is Some, otherwise return a default value.

What are Option Some and None in Scala?

An Option[T] can be either Some[T] or None object, which represents a missing value. For instance, the get method of Scala's Map produces Some(value) if a value corresponding to a given key has been found, or None if the given key is not defined in the Map.


2 Answers

That's the way type inference works in Scala...

First of all there is an implicit conversion from Int to BigDecimal:

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

That conversion allows for Int to be converted before the option is constructed:

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

If we try getOrElse on its own where the type can get fixed we get expected type Int:

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

However, this does not work (the problem you have):

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

Scala's implicit conversions kick in last, after type inference is performed. That makes sense, because if you don't know the type how would you know what conversions need to be applied. Scala can see that BigDecimal is expected, but it has an Int result based on the type of the Option it has. So it tries to widen the type, can't find anything that matches BigDecimal in Int's type hierarchy and fails with the error.

This works, however because the type is fixed in the variable declaration:

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

scala> v: BigDecimal
res4: BigDecimal = 1

So we need to help the compiler somehow - any type annotation or explicit conversion would work. Pick any one you like:

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
like image 199
yǝsʞǝla Avatar answered Nov 03 '22 00:11

yǝsʞǝla


Here is the signature for Option.getOrElse method:

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

The term B >: A expresses that the type parameter B or the abstract type B refer to a supertype of type A, in this case, Any being the supertype of Long:

val l: Long  = 10
val a: Any = l

So, we can do something very similar here with getOrElse:

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

Which brings us to your scenario: the returned type from getOrElse will be a Any and not a BigDecimal, so you will need another way to handle this situation, like using fold, per instance:

def writes(bar: Bar) = {
    val defaultValue = BigDecimal(0)
    JsObject(Seq(
        "id" -> JsNumber(bar.id.fold(defaultValue)(BigDecimal(_))),
        "name" -> JsString(bar.name)
    ))
}

Some other discussions that can help you:

  1. Why is Some(1).getOrElse(Some(1)) not of type Option[Int]?
  2. Option getOrElse type mismatch error
like image 27
marcospereira Avatar answered Nov 03 '22 00:11

marcospereira