Reputation: 8182
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
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
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
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