Fix #4415: remove show from repl#4437
Conversation
allanrenucci
left a comment
There was a problem hiding this comment.
Great to see this part of the REPL simplified! 🎉
| val string = value match { | ||
| case null => "null" // Calling .toString on null => NPE | ||
| case "" => "\"\"" // Sepcial cased for empty string, following scalac | ||
| case x => x.toString |
There was a problem hiding this comment.
Another special case for Array?
| .getDeclaredMethods.find(_.getName == sym.name.toString + "Show").get | ||
| .invoke(null).toString | ||
|
|
||
| .getDeclaredMethods.find(_.getName == sym.name.encode.toString).get |
There was a problem hiding this comment.
Does this assume a getter? Do we generate a getter for case like this:
> private[this] val foo = 1There was a problem hiding this comment.
It looks like we're fine:
scala> private[this] val foo = 1
1 |private[this] val foo = 1
|^^^^^^^
|Illegal start of statement: no modifiers allowed hereThere was a problem hiding this comment.
But I guess it doesn't hurt to avoid the .get...
There was a problem hiding this comment.
I was wondering if you could access the field instead
| else if (res.startsWith(str.REPL_SESSION_LINE)) | ||
| Some(res.drop(str.REPL_SESSION_LINE.length).dropWhile(c => c.isDigit || c == '$')) | ||
| else if (string.startsWith(str.REPL_SESSION_LINE)) | ||
| Some(string.drop(str.REPL_SESSION_LINE.length).dropWhile(c => c.isDigit || c == '$')) |
There was a problem hiding this comment.
I know the code above is not new, but I'm wondering why this is needed
There was a problem hiding this comment.
Ok I get it. It is to pretty print a class. However it quickly leaks:
scala> class Foo
// defined class Foo
scala> new Foo
val res0: Foo = Foo@7baf1f5a
scala> println(res0.toString)
rs$line$1$Foo@7baf1f5aIt is the same in scalac, so I guess that's fine. I imagine we could generate a default toString for classes in the REPL, but it is maybe not worth the trouble
There was a problem hiding this comment.
I would say it's fine to show the truth in this case, messing up with the object's toString might lead to more confusion...
| .invoke(null) | ||
| val string = value match { | ||
| case null => "null" // Calling .toString on null => NPE | ||
| case "" => "\"\"" // Sepcial cased for empty string, following scalac |
|
Since this fixes #4415, can you add a test for it: scala> lazy val x = ???
val x: Nothing = <lazy> |
|
I'm not convinced having a few hardcoded cases is better than a Show typeclass. I agree that the Show typeclass isn't ideal if it's only used by the REPL, but if we agree that having a standard Show typeclass in the standard library of Scala is useful, then it makes a lot of sense to use it for the REPL. |
|
/cc @felixmulder Maybe you want to defend Show too :). |
|
Obviously the repl should only be able to show things that have a Show instance 😜 But I'm not emotionally attached to it even though I agree with Guillaume's comment :) |
|
A proper |
|
So to fix If that's correct then we'd still have to fix implicit resolution to accommodate contravariant typeclasses at some point? |
This is already "fixed" in Dotty compared to scalac, unless you have something else in mind than what Dotty does now? See #4329 |
|
@smarter - that's what I thought as well. So I'm sort of curious what part is broken about our Maybe it's the base case for |
|
There isn't anything to fix about implicits, they are just the wrong tool for the job. I think people are used to have the REPL produce Strings for everything: scala> class Foo
defined class Foo
scala> val a = new Foo
a: Foo = Foo@377008dfUnless we plan to change the above (and update the entire Scala ecosystem to provide instances of Show), we shouldn't use a typeclass. For reference, here is what you get in ghci if you forget the deriving Show: Prelude> data Shape = Circle Float Float Float | Square Float -- deriving Show
Prelude> Circle 10 20 5
<interactive>:2:1: error:
• No instance for (Show Shape) arising from a use of ‘print’
• In a stmt of an interactive GHCi command: print it |
|
If you just want feature parity with Scala 2 and to close some minor bugs - then yes, you're absolutely right. I think we can do better with Dotty, but I leave the how up to you 😀 |
|
I think the best implementation of Re variance, it seems trait Show[-T] { def show(x: T): String }
implicit def showOption[T]: Show[Option[T]] = new Show { def show(x: Option[T]) = "showOption" } // XXX
implicitly[Show[Some[String]]].show(Some(""))Of course, that wouldn't fix the issue with showing values upcast to
Yes. The de-facto policy seems that we're trying to keep the same library as Scala2. |
|
@Blaisorblade We got ambiguous implicit errors when val x = List() // error ambiguous implicit. found charShow and floatShow... |
I see why, but adding a more specific (EDIT: in the example below trait Show[-T] { def show(x: T): String; def instName: String }
implicit def showOption[T]: Show[Option[T]] = new Show { def show(x: Option[T]) = "showOption"; def instName = "showOption" } // XXX
scala> implicitly[Show[Nothing]].instName
val res5: String = "showOption"
scala> implicit def showNothing: Show[Nothing] = new Show[Nothing] { def show(x: Nothing) = ""; def instName = "showNothing" }
implicit val showNothing: Show[Nothing]
scala> implicitly[Show[Nothing]].instName
val res6: String = "showNothing"scala> implicit def showList[T]: Show[List[T]] = new Show { def show(x: List[T]) = "showList"; def instName = "showList" } // XXX
implicit val showList[T] => Show[List[T]]
scala> implicitly[Show[Nothing]].instName
1 |implicitly[Show[Nothing]].instName
| ^
|ambiguous implicit arguments: both method showList and method showNothing match type Show[Nothing] of parameter ev of method implicitly in object DottyPredef |
The pattern is called implicit spaghetti, people try to avoid it ;) Seriously, if you need contravariance + an instance for |
|
Why not? There is nothing wrong with instances for new Show[Nothing] {
def show(n: Nothing): String = n
}is a perfectly valid I would not add The main reason people don't put variance on typeclasses in Scala2 is because implicit resolution w/ variance simply doesn't work there in most cases. |
|
Right, I mixed the two, the non sense is a |
|
@alexknvl Opened a separate issue on typeclass/implicit resolution. |
|
Note that Ammonite made the same decision and moved it's pretty-printing (https://github.com/lihaoyi/PPrint) to While it would be nice to "fix" implicit resolution to make In practice, manually recursing over |
|
@Blaisorblade your information is out of date, PPrint has been macro-free for almost exactly a year now. In fact it could probably be rewritten in Java if necessary; it doesn't require fancy Scala stuff at all. I would be highly surprised if you couldn't copy-paste the 10 files of the pprint codebase into dotc and have it compile out of the box |
|
@lihaoyi Ah thanks for the correction, I didn't find that in http://www.lihaoyi.com/PPrint/#TPrint, and I might have been misled by https://github.com/lihaoyi/PPrint/blob/005a51a509bae088dc69c4427a27280249ce8cf6/pprint/shared/src/main/scala-2.10%2B/TPrintImpl.scala. Is that dead code?
👍 yes, I think that's crucial for a REPL, the typeclass issues are probably more relevant for other typeclasses. |
|
@Blaisorblade oh TPrint is used just for printing the type signature, not the actual value. For some reason that hasn't caused any stability issues like the typeclass-based value pretty-printer has If you want to pretty-print values in dotty, just delete the TPrint source files when you copy-paste |
|
Having something similar to PPrint for the Dotty REPL would be nice. This can be implemented in a separate PR. |
|
I have a dead PR against scala/scala to "Introduce ReplPrinter as a hook for alternatives to replStringOf" that might be useful inspiration: scala/scala#5222. |
This PR removes the infrastructure around the
Showtype class in the repl. Instead we simply call .toString as done in scalac. It fixes #4415 and should make things more stable.The goal of Show was to add quotes around strings to make the output of repl copy pastable. I think using a type class with a default for
Anyis the wrong approach as it gives inconsistent results depending on the type: