Java coder trying to use Scala here...
I have a generated Java builder that has a method
public com.yada.SomeEntity.Builder setSomeValue(java.lang.Integer value) {
...
}
From Scala, I can call this with an integer value or with null (and the field is deliberately defined to be nullable).
In my Scala code, I have, let's say
val someInput: Option[Int] = someMethod()
The problem is that none of these work
builder.setSomeValue(someInput)- obviously, because it's an Option[Int] and not an Integer.builder.setSomeValue(someInput.orNull)- becausesomeInput.orNullisAnyand not an Integerbuilder.setSomeValue(someInput.orElseGet(null)- that's also anAny
I can do this
builder.setSomeValue(if (someInput.isDefined) someInput.get else null)
Or alternatively, I can map something on someInput, but that breaks up the fluent style of the builder and just looks ugly.
Is there a pretty way to handle this in Scala?
2 Answers 2
TL;DR
A canonical solution is to use fold:
methodThatTakesNullableInteger(intOption.fold(null)(Int.box))
Why is it "canonical"?
All Scala collections that arise from algebraic data types have a canonical eliminator that is usually called fold-something:
Option[A]hasfold[B](none: B)(some: A => B): BList[A]hasfoldRight[B](nil: B)(cons: (A, B) => B): BEither[X, Y]hasfold[B](left: X => B, right: Y => B): B
Generally, these methods serve the same purpose: they take a value of type Coll[A] and eliminate it into a value of type B.
In each case, the signature of the method resembles 1:1 the structure of the collection. In a very specific sense, having such a fold method is equivalent to being that collection (keywords: initial F-algebras and all that racket).
In your case, you have Option[Int] (A = Int), and you want to eliminate it into B = java.lang.Integer. Therefore, you simply take your value of type Option[Int], and invoke fold with two arguments:
- a
n: java.lang.Integerto handle the caseNone(here:null) - a
f: Int => java.lang.Integerto handle the caseSome(here:Int.box)
and you obtain your java.lang.Integer, as desired.
1 Comment
.map(Int.box).orNull more readable 🤷I have to admit I'm slightly surprised that there's no implicit conversion in this case. Since they do have issues, it's probably for the best. As suggested in a comment, someInput.map(Int.box).orNull or someInput.fold(ifEmpty = null)(Int.box). If you want this conversion to happen automatically, you can use an implicit conversion that does it for you:
def setSomeValue(value: java.lang.Integer): Unit =
println(s"value is $value")
def someMethod(): Option[Int] =
Some(1)
import scala.language.implicitConversions
given boxInt: Conversion[Int | Null, java.lang.Integer] with
def apply(x: Int | Null): java.lang.Integer =
x match {
case n: Int => Int.box(n)
case _ => null
}
setSomeValue(someMethod().orNull)
You can play around with this code here on Scastie.
Please note that implicit conversions have drawbacks that might make it a poor fit for your use case, so pay attention (that's why you need to explicitly opt in). In this particular case, they might hide a boxing operation that you might not want to perform unless necessary due to performance reason. More in general, I found this answer to give a good explanation on the drawbacks of implicit conversions, although I would argue that, modulo performance considerations, this is probably a decently good use case. Finally, I can recommend to have a look at the chapter on implicit conversions from the Scala book.
builder.setSomeValue(someInput.map(Int.box).orNull)- You have to manually box the value. - Another alternative would bebuilder.setSomeValue(someInnput.fold(ifEmpty = null)(Int.box))