User1291
User1291

Reputation: 8182

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?

Upvotes: 2

Views: 1218

Answers (3)

Régis Jean-Gilles
Régis Jean-Gilles

Reputation: 32719

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.

Upvotes: 2

0__
0__

Reputation: 67280

No, you cannot change a method of a value "ad-hoc". You will need to rely on a type class instead

trait Show[A] {
  def show(x: A): String  // replaces the `toString` method
}

// allows you to write foo.show instead of implicitly[Show[Foo]].show(foo)
implicit class ShowOp[A](private val x: A) extends AnyVal {
  def show(implicit s: Show[A]): String = s.show(x)
}

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

val foo = Foo("bar")
foo.show   // error -- no implicit for Show[Foo]

// define the Show type class for foo
implicit object ShowFoo extends Show[Foo] {
  def show(f: Foo) = s"Foo(${f.s})"
}

foo.show  // ok: "Foo(bar)"

Upvotes: 2

Gabriele Petronella
Gabriele Petronella

Reputation: 108091

You're basically asking whether you can patch the toString method of a specific instance at runtime.

That's not possible in a language like scala.

You can extend Foo with a custom method using implicits (see the type class example in 0__'s answer), but there's no way to change the implementation of the toString method.

Upvotes: 0

Related Questions