Search code examples
scalaoverridingtostringpretty-print

Scala - bypass overridden toString method


Given a case class that - unfortunately - overrides the toString method, any way to bypass that method?

I.e:

case class Foo(s:String){
    override def toString = s
}

Then

val foo = Foo("Hello World")
println(foo)

will yield

Hello World

If I am just given foo (but not Foo) can I do anything to foo so that it will print

Foo(Hello World)

instead of just the string?


Solution

  • Using a Show type class as shown by 0__ is a good option. Unfortunately toString is so prevalent (a lot of code relies on it to format a string given an object) that there are a lot of case where a type class won't you do any good. By example, if you have a List[Foo], calling toString on it will call Foo.toString. The right solution using a type class will be to define an instance for lists, which will itself call show on the Foo instances. But this only pushes the problem further away, because it might very well happen that you are passing this list to some third party code that you have no control on, and that will call List.toString (instead of Show.show). In this specific case, the only viable solution is to wrap Foo class in your very own class (say MyFoo) and override toString there. Obviously, this will only be useful if you can change your List|[Foo] into a List[MyFoo]

    implicit class MyFoo(val foo: Foo) extends AnyVal {
        override def toString = s"Foo(${foo.s})"
    }
    object MyFoo {
     implicit def unwrap(myFoo: MyFoo): Foo = myFoo.foo
    }
    

    Repl test:

    scala> val list1: List[Foo] = List(Foo("foo"), Foo("bar"))
    list1: List[Foo] = List(foo, bar)
    
    scala> val list2: List[MyFoo] = List(Foo("foo"), Foo("bar"))
    list2: List[MyFoo] = List(Foo(foo), Foo(bar))
    

    As you can see, the string representation of list2 uses you very own toString implementation. Clearly, this solution is not ideal. It might even be totally impractical in many situations (by example, you cannot pass a MyFoo to a method expecting a Foo). If anything, it shows that relying on a toString method slapped right in Any is not the best design, but alas we have to live and work with a lot of code (including the whole java ecosystem) that does just that.

    So as clunky as the above solution is, if you don't have control over who will call Foo.toString (meaning that you cannot change that call to anything else, such as Show.show) you might not be able to do much better.