Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala - how to pattern match when chaining two implicit conversions?

I wrote a method to parse Metrics data and at first faced a problem with the type of transactionMap which is a java.util.Map. And I solved it using JavaConverters.

def parseMetrics(metric: Metric) = {
    import scala.collection.JavaConverters._
    metric.transactionMap.asScala.values.map {
      case false => "N" 
      case true => "Y"
    }.toList

But after that I got an error while pattern matching true and false values: pattern type is incompatible with expected type, found: Boolean, required: java.lang.Boolean

As far as I understand Scala does not chain two implicit conversions. Is there a way to fix it using JavaConverters?

like image 955
samba Avatar asked Dec 18 '22 18:12

samba


2 Answers

The other answer provides a reasonable way to solve this problem, but doesn't show why you're running into it or how the approach it proposes works.

The Scala standard library does provide an implicit conversion from java.lang.Boolean to scala.Boolean, which you can see in action by using reify in a REPL to desugar some code that uses a Java boolean in a context where a Scala boolean is expected:

scala> val x: java.lang.Boolean = true
x: Boolean = true

scala> import scala.reflect.runtime.universe.reify
import scala.reflect.runtime.universe.reify

scala> reify(if (x) 1 else 0)
res0: reflect.runtime.universe.Expr[Int] =
Expr[Int](if (Predef.Boolean2boolean($read.x))
  1
else
  0)

The problem is that simply trying to match a java.lang.Boolean value against true or false isn't sufficient to trigger the conversion. You can check this by defining your own types where you can be sure you know exactly what conversions are in play:

scala> case class Foo(i: Int); case class Bar(i: Int)
defined class Foo
defined class Bar

scala> implicit def foo2bar(foo: Foo): Bar = Bar(foo.i)
foo2bar: (foo: Foo)Bar

scala> Foo(100) match { case Bar(x) => x }
<console>:17: error: constructor cannot be instantiated to expected type;
 found   : Bar
 required: Foo
       Foo(100) match { case Bar(x) => x }
                             ^

This is a language design decision. It would probably be possible to have the implicit conversions applied in these scenarios, but there's also probably a good reason that they aren't (off the top of my head I'm not familiar with any relevant discussions or issues, but that doesn't mean they don't exist).

The reason Andy's solution works is that the java.lang.Boolean is in a position where the compiler expects a scala.Boolean (a condition) and is willing to apply the Predef.Boolean2boolean conversion. You could do this manually if you really wanted to:

def parseMetrics(metric: Metric) = {
  import scala.collection.JavaConverters._
  metric.transactionMap.asScala.values.map(Predef.Boolean2boolean).map {
    case false => "N" 
    case true => "Y"
  }.toList
}

…but to my eye at least pattern matching on Boolean is a little clunkier than using a conditional.

like image 59
Travis Brown Avatar answered Dec 30 '22 13:12

Travis Brown


Use if/else rather than a match statement for Boolean checking:

def parseMetrics(metric: Metric) = {
    import scala.collection.JavaConverters._
    metric.transactionMap.asScala.values.map {
      x => if (x) "Y" else "N"
    }.toList

My suspicion is that within the if statement the java.lang.Boolean (which I presume x is here) can be coerced to Boolean via import scala.collection.JavaConverters._... but the match statement doesn't do the same coercion, but would have to be made explicitly (or match on the java.lang.Boolean values).

like image 27
Andy Hayden Avatar answered Dec 30 '22 12:12

Andy Hayden