Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implicitly wrap a value that can be null or an array into an Scala Option

I have this Java class in a Jar file included as a dependency of an Scala program (like the Axis jar):

class MyClass {
    private String[] someStrings;
    public String[] getSomeStrings() { return someStrings; }
}

In my Scala program I have a Java API that return an instance of MyClass instance of MyClass to my program in Scala:

val myInstance = JavaAPI.getInstanceOfMyClass()

Then I try to use the someStrings array in my Scala program but it's null (let say that it wasn't properly initialized)

for(str <- myInstance.getSomeStrings()) ...

So this throws a NullPointerException.

I've found that in order to use it in the for comprehension I can wrap it into an Option so that it handles the NPE properly.

for(str <- Option[Array[String]](myInstance.getSomeStrings).getOrElse(Array[String]())

But that doesn't look OK to me.

Is there a way to create like an implicit method that takes that value even if it's null and wrap it into the Option, like:

implicit def wrapNull(a: Null): Option[Nothing] = None
implicit def wrapArray(a: Array[String]): Option[Array[String]] = Some(a)

So that when I do:

for(str <- myInstance.getSomeStrings())

I don't get the NPE

like image 577
jmdev Avatar asked Jan 16 '23 22:01

jmdev


1 Answers

edit:

A mapas well as a flatMap always have to return the same type, on which they are called. If you have a List, you will always get a List back from map. The same is true for an Option. If you try to mix 2 types in a flatMap, this will most likely not work. What should

Some(Array(1,2)).flatMap { x => 
  x.map { _ * 2 }
}

return? Some(2,4) is not possible. So you get a type error. For this reason you have to do a nested map { map } instead of flatMap { map }.

In your case it would work like this:

case class A(b: B)
case class B(c: String)

val result = for(as <- Option(Array(A(B("foo")), A(B("bar"))))) yield {
  for(a <- as; b <- Option(a.b); c <- Option(b.c)) yield {
    c
  }
}

The first for takes an Option[Array[A]] and returns an Option[Array[String]]. The nested for takes an Array[A] and returns an Array[String]. They both satisfy the monad laws. At the end you can safely call getOrElse on result to unwrap the value if you want to.

original:

You could just do

val result = Option(myInstance.getSomeStrings).map { x =>
  x.map { y =>
    // do stuff with strings
  }
}

or

val result = for(x <- Option(myInstance.getSomeStrings)) yield {
  x.map { y =>
    // do stuff with strings
  }
}

You don't need to write the types because of the type inference and you don't need getOrElse, because the map will not be executed for None. You can then simply do a getOrElse on result, if you need to unwrap the value.

like image 153
drexin Avatar answered Jan 25 '23 12:01

drexin