-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix #4415: remove show from repl #4437
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this assume a getter? Do we generate a getter for case like this:
> private[this] val foo = 1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I guess it doesn't hurt to avoid the .get...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know the code above is not new, but I'm wondering why this is needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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@7baf1f5a
It 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: Special
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@377008df Unless 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
Show
type 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
Any
is the wrong approach as it gives inconsistent results depending on the type: