Add compiler-support for deriving Debug type class#4390
Add compiler-support for deriving Debug type class#4390JordanMartinez wants to merge 6 commits intopurescript:masterfrom JordanMartinez:jam/derive-debug
Debug type class#4390Conversation
|
What does this get us over |
I don't have a strong opinion on this, but anecdotally I would say most questions in Discord around deriving (and |
|
If the verbosity of the required declarations is the problem, I'd rather see #3824 resumed, or whatever other syntactic improvements would benefit instance deriving in general. Isn't bringing I don't mind grandfathering in I don't want to come across as super-dogmatic about this either; it just seems to me like we should hold some line against implementing everything in the compiler, and I don't know if this makes the cut. |
|
I think an interesting question at this point is: can we provide something more than what |
|
Another consideration with generic vs explicitly supported deriving is runtime performance, but in this particular case I don't feel like it is a factor because |
Does deriving via help? Honestly to me it feels like you need an even more obscure feature to use and explain to newcomers to get basic debugging support. Deriving via is neat, but it comes with a lot of implicit knowledge around newtypes and coercions directing instance resolution. My main concern is that this is meant to be a pretty pervasive inclusion, and it’s also one of the first things newcomers want to do. I think reducing that friction as much as possible, and making it feel straightforward and non intimidating is as justifiable as performance. |
That's a solid point. But the way I see it, the barrier to being able to point newcomers at generic implementations of things like For example, if we had #3067, we could make things even more straightforward ( Our Project Values talk about preferring fewer powerful general-purpose features over many special cases, and the desirability of solving problems downstream of the compiler. My concern here is that we may be shying away from those values because the design of the general-purpose features seems daunting, whereas we all know how to implement a new compiler-derived class. But these proposals are years old and most of them are following stable language features in Haskell; we should not be afraid to move forward on them. And doing so will benefit larger parts of the PureScript experience than this one specific class. |
|
Is there a path to making a Generic derived implementation as straightforward to use as stock deriving? Honestly, it kind of drives me nuts that the path we don't want to take is the path with the most straightforward syntax and semantics for downstream users. Haskell has default signatures and implementations, which helps a lot. But we have no proposal for defaults because it's a notorious foot-gun even in Haskell land. I know that I said I didn't have a strong opinion, but even: Is frankly, completely obtuse to a newcomer compared to: When it comes to just wanting to show something in the repl. The ideal situation for me would be to have just that, but still let it be derived via Generics, possibly through an ad-hoc solved instance if Generic is not otherwise declared. |
|
For context, I opened this PR partly to see what discussion would result from doing that. This was my thinking: while I could migrate Having read through these comments, I'm actually surprised that no one made an objection to this PR on the basis that I'll respond to some of the above comments now.
I do agree with Ryan that deriving
Still, I agree with Nate that the second version above is easier for newcomers. Those unfamiliar with FP languages will both 1) find it easier to understand the second over the first and 2) be able to use
While the REPL would benefit from this, it's actually not my main intent here. I feel like Try PureScript is the REPL of choice because it also allows one to run code on a browser, the main target of PureScript.
This is an interesting idea. If I'm understanding you correctly, writing would implicitly become If I don't explicitly derive Having said all that, I think I agree with Ryan more in that this shouldn't be done, at least not yet. I'd like to first verify that the API defined by the On a different note, I'd like to propose a new general compiler feature based on Nate's idea above. This would entail a lot of work, so it's not quite relevant as to whether this deriving Debug should be supported or not. However, it's an idea that could resolve some of the issues raised in this PR. What if the data Foo = ... deriving (Generic, Debug)If we had annotations, what if we could define a type class like this? And then all type classes that don't need special compiler support (e.g. class Enum a where
@derivable Data.Enum.Generic.genericPred
pred :: a -> Maybe a
@derivable Data.Enum.Generic.genericSucc
succ :: a -> Maybe aWhenever the compiler comes across a type class that is being derived using "stock-deriving type class syntax", it either
The end result is syntax that
Rather than "blessing" this type class, this idea would implement a general compiler feature that is otherwise beneficial to multiple type classes, not just Let's assume we're a new user. Here's the error message "workflow" I might go through when I try this out for the first time: module Foo where
data Foo
= ...
deriving DebugCompiler error 1: module Foo where
import Data.Generic.Rep (class Generic)
data Foo
= ...
deriving (Generic, Debug)Compiler error 2: module Foo where
import Data.Generic.Rep (class Generic)
import Data.Debug.Generic (genericDebug)
data Foo
= ...
deriving (Generic, Debug)And then the code works. One potential downside of this idea is where the module Data.Enum where
class Enum a where
@derivable genericPred
pred :: a -> Maybe a
@derivable genericSucc
succ :: a -> Maybe a
genericPred :: ...
genericSucc :: ...And then we could write: module Data.Foo where
import Prelude
import Data.Generic.Rep (class Generic)
-- Assuming `genericDebug` wasn't included in `import Prelude`
import Data.Debug (genericDebug)
import Data.Enum (class Enum, genericPred, genericSucc)
data Foo
= ...
deriving (Eq, Ord, Generic, Debug, Enum) |
Having experience with FP does not necessarily correlate with knowledge of typeclasses, instance resolution, newtypes, deriving, or The anecdotal data I do have is years of directing confused newcomers how to get Show support. After all, it's as simple as
Is this a significant distinction in practice? I think "REPL" can be a stand-in for any introductory environment.
To be clear, I'm not suggesting the compiler opt the user into a Generic instance that is generally available. If the compiler can generate a Generic instance, then it can solve an ad-hoc Generic constraint (like it does for IsSymbol) for the purposes of deriving such that it doesn't leak, which could provide you with a uniform deriving mechanism that does not require users to know about Generics or deriving via. |
|
Dumb question: why do we want users to specify (Not sarcasm or irony; this is a genuine question and if we can't answer it then I support this as a plan of action, contra my previous stance. If there is some principle that makes this a bad idea then I want to see how that principle applies to the other proposals.) |
I think in general we've landed on the side of "users should opt-in to typeclass semantics", though we've certainly violated that for |
|
I was going to suggest something similar actually. A class for debug printing I'd be 100% okay with it being magical where you never have to declare anything for it, and instances essentially come into existence on demand (to avoid bloating the JS with unused instances - although I guess that matters less with the tooling we have available now). It doesn't even have to be a class mechanism, maybe it's just a function in |
|
I have been annoyed at Rust for having to add a Debug instance for just printing stuff out, so I would love an automagic deriving of this. Though it might be weird to have to implement this manually in some cases (e.g. when there are functions involved, etc) |
I'll answer this from the perspective of using Below are the kinds of types where the
|
I don't think that'd work as well—imagine we provide a
Yeah totally, that's why I proposed a different class in addition to |
|
I'm slowly coming around to adding some compiler deriving or compiler magic for I think an automatically derived The other advantage of not requiring But other than that, compiler deriving won't/can't do anything substantial that FWIW I don't like Jordan said:
That was at the back of my mind. I honestly hadn't had a good look at it until it came up the other day. One option to alleviate design concerns is that we could have typeclasses-on-typeclasses, and be generic over the debugging output type 😂 That is, instead of class (DebugStruct out) => Debug out myType where
debugInto :: myType -> outwhere Nate said:
We did provide ways for users to opt-in to |
|
Ryan said:
Yeah, I like this idea of import Data.Debug.Type as DT
import Data.Debug as D
class AutoDebug a where
debug :: a -> DT.Repr
-- Works as though "backtracking" was enabled
instance (D.Debug a) => AutoDebug a where
debug = D.debug
else instance AutoDebug a where
debug = <compiler implementation>
-- Code works on AutoDebug
diff :: forall a. AutoDebug a => a -> a -> Diff
diff = ...
-- Foo uses AutoDebug
data Foo = Foo
data Bar = Bar
-- Bar's AutoDebug uses its custom Debug instance
instance D.Debug Bar where
debug = ...Verity said:
Why would you want warnings here?
Due to 😂, are you being sarcastic? I can't tell 😄. On another note, that does raise the question as to what those |
|
I didn't mean it sarcastically, I just expect that most people will think it's overkill :) Re warnings: so that users understand it is automagic behavior that shouldn't be relied on, meaning that they should probably write or upstream the instances if they want them, because otherwise someone could come along and write an instance that silently changes its behavior! Of course you probably shouldn't be relying on its behavior at runtime anyways, but your tests are more likely to rely on it, you know? Also I'm not sure that having a separate |
|
One thing I personally find irritating is the name However, if I'm a newcomer and I come from a language where debugging actually means using a debugger it's not clear that deriving So I would like to suggest a name like Sorry for putting another door into the bike shed. |
I disagree that this would be confusing for a few reasons:
As for why I think this should still be named
|
|
@JordanMartinez |
|
Closing as this hasn't had any feedback since it's been opened. Moreover, the PRs I submitted to core libraries haven't received any feedback either. While I don't think this idea is dead, I do think the current approach isn't the one we'll take. |
Description of the change
See purescript/purescript-prelude#272 for more context.
By adding compiler support, it means most other types can get a
Debuginstance by just deriving it. Moreover, it means adding support across core libraries would be easy because each type could have its instance derived.This implementation does not add support for deriving opaque types (e.g.
foreign import data X :: Type) because one will likely want to write those types'Debuginstances by hand.Checklist: