Show null compatibility with scala string interpolator
We have a lot of java things we interact and they can throw exceptions or have null in certain pojo fields to where if we use them in a string interpolator it will just print null
val foo = Foo(a, null)
log.info(s"found a foo ${foo.secondField)}")
val exception = new RuntimeException()
log.error(s"something happened ${exception.getMessage}", exception)
We tried to migrate to show but it blows up and does not give you a good error message to indicate where the problem is. I asked in Discord and here's the feedback:
- don't use nulls (it's very hard to do this especially when integrating with a lot of java teams/libraries)
- it's a bug, file a bug (doing that here)
- write my own version of show SafeShow and use that.
I've done number 3 and created SafeShow but incorporating it into our legacy monolith is an effort itself so I thought I'd open this to see if anything could be done in cats itself. (I've basically duplicated the show syntax, interpolator and use the typename as required field for constructing the Show type class instance using SafeShow and log the typename and the exception when we recover from NPE before printing "null"). This is not something I really want to maintain although it's not that much code (less than 100 lines of main code). Also most of our libraries just use plain Show so we have to go and change all those as well.
The ability to adopt and use things from cats for newbies is also a hurdle. When do I use our safe show and when do we not. It's a lot for newbies to absorb and makes the interop with Java stuff harder. It's also a hurdle when things that seem like they should be equivalent are not.
@BusyByte , thank you for the report!
Would you mind adding an example with Show that causes the issue please?
@satorg here you go
import cats.implicits._
case class Foo(firstField: String, secondField: String)
implicit val showFoo: Show[Foo] = Show.fromToString
val foo = Foo(a, null)
println(show"found a foo ${foo.secondField)}")
val exception = new RuntimeException()
println(show"something happened ${exception.getMessage}")
val message = foo.secondField.show
val anotherMessage = exception.getMessage.show
println(message)
println(anotherMessage)
val foo2: Foo = null
println(show"found a $foo2")
val message3 = foo2.show
println(message3)
Maybe a fix would be to make fromToString work correctly with null so it returns "null" or something. That should address the Java interop issue without changing behavior for people that set their own Show instances.
Or we could bake support for null into the macro.
@johnynek I vote for macro
I don't think it's a huge deal either way, but I would generally expect Show.fromToString[A].show(a) === show"${a}", which suggests putting it in fromToString`.
Note that String.valueOf does what we want, assuming we want the representation of null to be "null".
I like making fromToString use String.valueOf
hi, @rossabaker I’d like to work on this issue.
From the discussion, the plan seems to be updating Show.fromToString to use String.valueOf(a) instead of a.toString to safely handle null values.
I’ll proceed with this change and add a corresponding test case to verify that Show.fromToString.show(null) returns "null".
Please let me know if this approach sounds good before I start.
I can work on making the Show typeclass null-safe for Java interop scenarios. I understand the pain point of NPEs when using string interpolation with Java libraries. I'll explore implementing a safe variant that gracefully handles nulls while maintaining functional programming principles and type safety.