(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
Show instance, for example, (->). This doesn't seem so bad, because what use would showing 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 use Generic; you have to write out Show instances 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
Show instances, the more dangerous show becomes; given that it works on almost anything, and that the return type is just String (rather than something that mentions the input type like pure, for example) it's very easy to call show on 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
debugShow on each.
- If it's an array, simply
map debugShow.
- If it's an ADT defined in PureScript code (ie
data or newtype), do the same thing as the what the derived Show instance in Haskell would do.
- Types defined via
foreign import data could 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 id might 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.
(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:
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.String, it's just too tempting to abuse it for serialization (as we have discovered in the Haskell community).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!@jonsterling suggests having
print :: forall a. a -> Unitinstead, 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 onpurescript-foreign, this is just to illustrate):Where the return type of
debugShowis anything that is suitable to be passed toconsole.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. Tryconsole.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 inpurescript-debug, and which internally doesconsole.log <<< debugShowbefore returningunit.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:
debugShowon each.map debugShow.dataornewtype), do the same thing as the what the derivedShowinstance in Haskell would do.foreign import datacould probably be left alone.print idmight produce"<Function: forall a. a -> a>".and so on.
If
printoccurs in your program, it generates a compiler warning, which helps you avoid accidentally leaving them in.printcould 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 theEfftype intoPrimthen$PSCI.Supportcould have no library dependencies, which would resolve #1441.