-
Notifications
You must be signed in to change notification settings - Fork 571
Description
(Vaguely related to purescript/purescript-prelude#46.)
I think @jonsterling is on to something here (I recommend reading at least the first few replies as well): https://twitter.com/jonsterling/status/670091766549344257
I've felt vaguely uneasy about the Show type class for a long time and I think this hits the nail on the head. I think our current Show type class is unsuitable for debugging / use in the REPL (which ought to be its main aim) because:
- Some types have no
Showinstance, for example,(->). This doesn't seem so bad, because what use wouldshowing a function be anyway? But it can be irritating when you want to print larger structures which contain types like(->). In particular, at the moment, you can't useGeneric; you have to write outShowinstances which skip over the "unshowable" things yourself. - When you have a function that turns almost anything into a
String, it's just too tempting to abuse it for serialization (as we have discovered in the Haskell community). - The more types that do have
Showinstances, the more dangerousshowbecomes; given that it works on almost anything, and that the return type is justString(rather than something that mentions the input type likepure, for example) it's very easy to callshowon the wrong thing. It's almost like using a dynamic language! - Records can't have instances, even though there's no technical reason that they couldn't be printed for debugging.
@jonsterling suggests having print :: forall a. a -> Unit instead, similar to the existing purescript-debug library.
I think we could improve this situation significantly by having a compiler-supported Show replacement.
I propose that we add a type class to the compiler. Let's call it Debug (it wouldn't depend on purescript-foreign, this is just to illustrate):
class Debug a where
debugShow :: a -> Foreign
Where the return type of debugShow is anything that is suitable to be passed to console.log. Sometimes this will just be a String, but often I think it's a good idea to leave other types as they are, as browsers can offer nicer UIs. Try console.log({foo: 1}) in Firefox or Chrome for instance.
This type class would be invisible to the user, if possible, but we also provide a function which is visible, something like print :: forall a. (Debug a) => a -> Unit, or perhaps closer to the types in purescript-debug, and which internally does console.log <<< debugShow before returning unit.
We add a special case in the compiler so that every type is an instance of Debug, including functions and records. Defining your own instances is not allowed. This way, we don't need to worry about orphans and all that (I think).
These instances would probably do something like:
- If it's a record, iterate over the properties and call
debugShowon each. - If it's an array, simply
map debugShow. - If it's an ADT defined in PureScript code (ie
dataornewtype), do the same thing as the what the derivedShowinstance in Haskell would do. - Types defined via
foreign import datacould probably be left alone. - Functions could be left alone. Browsers already do a decent job of printing these. Alternatively, we could include the type:
print idmight produce"<Function: forall a. a -> a>".
and so on.
If print occurs in your program, it generates a compiler warning, which helps you avoid accidentally leaving them in.
print could also include the current file name and line number.
I realise this is, to some extent, at odds with the current minimalistic approach of the compiler. But to me, it appears promising enough to justify bending the rules a little. And I think it would allow us to get rid of Show. This would not only make me happy not only because Show unnerves me, but also because it would take us most of the way to removing all the runtime dependencies of psci — If we also moved the Eff type into Prim then $PSCI.Support could have no library dependencies, which would resolve #1441.