2

I was trying to create an array containing all Int, Long and Double types. However, I found that the Long and Int types are automatically converted to Double. Here's a minimal example,

object HelloWorld {
  def main(args: Array[String]): Unit = {  
    val types = Array[Byte]('i', 'l', 'd', 'd')
    val array = Array[String]("1", "2000000", "20.0", "2020.0")
    val values = array.zip(types) map { case (s, t) => 
      t match {
        case 'i' => s.toInt
        case 'l' => s.toLong
        case 'd' => s.toDouble
      }
    }
    values.foreach { println }
  }
}

The result is

1.0
2000000.0
20.0
2020.0

How can I avoid such conversion? Thanks.

Suma
  • 33,181
  • 16
  • 123
  • 191
Max Wong
  • 694
  • 10
  • 18
  • Are you getting any deprecation warnings? e.g., "Widening conversion from Long to Double is deprecated because it loses precision. Write `.toDouble` instead." https://scastie.scala-lang.org/04t4VlyPTPeN6EokyPjA1Q – Alonso del Arte Oct 19 '20 at 18:50
  • Does this answer your question? [implicit conversion over multiple levels, why does int to double automatically work?](https://stackoverflow.com/questions/28252060/implicit-conversion-over-multiple-levels-why-does-int-to-double-automatically-w) – jrook Oct 19 '20 at 18:59
  • 1
    To prevent this, you can explicitly declare the type of your array: `val values : Array[Number]` , will get you an array of `Number`s which is the super type of all the types in the array. – jrook Oct 19 '20 at 19:01
  • @jrook I made a Scastie snippet: https://scastie.scala-lang.org/i5nOF5BDQVKXI5I61zceMg It works. I think you should write this up as an answer... – Alonso del Arte Oct 19 '20 at 19:12
  • @AlonsodelArte Yes, when I tried on scastie, I get that warnings. But I want to get long and converting to `Double` will cause precision loss – Max Wong Oct 19 '20 at 20:03
  • 1
    @jrook I read through the problem in your link. I think that's probably the cause of my issue – Max Wong Oct 19 '20 at 20:06
  • Yes. Indeed, that works! Thanks – Max Wong Oct 19 '20 at 20:07
  • @YanqiHuang, going with `AnyVal`, you will have to cast array elements to get the right type out of the result array (e.g. `values.head.asInstanceOf[Int]`). This could lead to `ClassCastException`. While `Number` is a java construct, using its `doulbeValue()` or `intValue()` won't throw exceptions which may or not help you in this case. – jrook Oct 19 '20 at 20:15

3 Answers3

3

The expression

t match {
  case 'i' => s.toInt
  case 'l' => s.toLong
  case 'd' => s.toDouble
}

infers to Double because Scala 2 calculates weak least upper bound. One option is to use type ascription to tell the compiler explicitly the regular least upper bound, for example,

(t match {
  case 'i' => s.toInt
  case 'l' => s.toLong
  case 'd' => s.toDouble
}): AnyVal

Scala 3 drops the notion of weak conformance

drops the general notion of weak conformance, and instead keeps one rule: Int literals are adapted to other numeric types if necessary.

so your example would work as intended.

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
2

This is because the compiler tries to come with a least surprising type of the match expresion, which is a type all those types (Int, Long and Double) can be converted to:

t match {
    case 'i' => s.toInt
    case 'l' => s.toLong
    case 'd' => s.toDouble
  }

You can use a type ascription or an assignment to provide your own type instead, which may be Numeric or AnyVal.

case (s, t) => (t match {
    case 'i' => s.toInt
    case 'l' => s.toLong
    case 'd' => s.toDouble
  }):AnyVal

or

    val result: AnyVal = t match {
      case 'i' => s.toInt
      case 'l' => s.toLong
      case 'd' => s.toDouble
    }
    result

Another alternative is to use the :AnyVal type ascription on any of the case results, which will also make the compiler to give up trying coming up with a more sensible type:

    t match {
      case 'i' => s.toInt:AnyVal
      case 'l' => s.toLong
      case 'd' => s.toDouble
    }
Suma
  • 33,181
  • 16
  • 123
  • 191
0

This is not the cleverest way: make a list of lists, then flatten.

val types = List[Byte]('i', 'l', 'd', 'd')
val array = List[String]("1", "2000000", "20.0", "2020.0")
val values = array.zip(types) map { 
  case (s, t) => t match {
    case 'i' => List(s.toInt)
    case 'l' => List(s.toLong)
    case 'd' => List(s.toDouble)
  }
}
values.flatten

Here's the Scastie snippet: https://scastie.scala-lang.org/tpLbvlg1TrKmQsqeZRsDTg

P.S. Arrays are provided in Scala mostly for the sake of Java interoperability. Otherwise prefer lists.

Alonso del Arte
  • 1,005
  • 1
  • 8
  • 19