Newtypes Have Wrong Haddocks on GHC 8.10
Exporting something like newtype Foo = Foo { runFoo :: Int } explicitly, a la (Foo(..)) is presented as a data declaration, and does not expose runFoo to the user. For example:
with the export CanT(runCanT) will be turned into this on GHC 8.10.x:
And this on 8.8.x:
Hi Emily, thanks a lot for the report. I'm surprised this didn't trigger any golden test.
I'm not actually seeing this as a difference between GHC 8.8.4 and 8.10.7. I get the output shown in your first screenshot with both versions of GHC. Can you please try your test case again with these GHC versions?
To avoid any confusion, I am running haddock test.hs -h -o out on the following test.hs file:
module Bug1366 ( CanT(runCanT) ) where
-- | A monad transformer for the pointed product,
-- parameterized by:
--
-- * @a@ - the value on the left
-- * @b@ - the value on the right
-- * @m@ - The monad over a pointed product (see: 'Can').
--
-- This monad transformer is similar to 'TheseT',
-- except with the possibility of an empty unital value.
--
newtype CanT a m b = CanT { runCanT :: m (Can a b) }
data Can a b = Can
If I change the export to CanT(..), then I get the output from your second screenshot.
Although it may be surprising that Haddock's output shows data for a newtypedeclaration, this is the expected behavior when a newtype is exported without any constructors (i.e., as an abstract type). See also the relevant code, which has been around since at least 2006.
If you want Haddock to show runCanT without exporting CanT's constructor, try module M ( CanT, runCanT ).
I think a case could be made for changing this behavior so that abstract newtypes are output as newtype and not as data, but, as it stands, it looks like the issue you reported is the expected behavior and does not differ between the latest patchlevels for GHC 8.8 and 8.10. If you are still seeing a discrepancy, please provide the specific code for which you are producing documentation, along with the specific GHC versions you are using.
Hi Steve,
It is indeed a problem with the export forms CanT(runCanT) and CanT(..) for both GHC versions. While this is expected behavior as you point out, the rationale behind the behavior is incorrect. The difference between a single parameter datatype and a newtype is distinguishable by potential performance impact (I cannot, for instance, unpack a newtype parameter or lazify/strictify on a whim), nor can I apply the same language extensions to the two. I believe this behavior is now incorrect and therefore obsolete, and while it doesn't point to a concrete bug in the code sense, it is both wrong in terms of its rationale, and how counter-intuitive it is to begin with. My documentation engine should not be choosing a representation that differs from what I type, even for semantically identical expressions.
As an aside, what's your interest in this issue?
The difference between a single parameter datatype and a newtype is distinguishable by potential performance impact (I cannot, for instance, unpack a newtype parameter or lazify/strictify on a whim), nor can I apply the same language extensions to the two.
Can you please provide an example piece of code where a library user can distinguish whether an abstract data type has an algebraic datatype (data) implementation or a renamed datatype (newtype) one? I think all the examples that you provided only show that distinguishing between the two implementations is important to library authors. It would help to have a concrete example of a case where the rationale in the existing test case does not hold for library users. That way, we could document the rationale for the change when updating the test case.
it is both wrong in terms of its rationale, and how counter-intuitive it is to begin with.
I'm sympathetic to your feeling that the current behavior is counter-intuitive, but I do think that it matches the abstraction barriers defined by the Haskell Report. Perhaps the cases you have in mind are cases where GHC broke abstraction barriers with its extensions.
As an aside, what's your interest in this issue?
I'm just trying to help resolve some of the open bugs in various projects I use. This issue just happened to be the first on the list of open Haddock bugs.
I'll do that if @Kleidukos asks. As far as etiquette is concerned, you shouldn't try and larp as a maintainer.
PS:
defined by the Haskell Report
GHC deviated from the Haskell Report as of GHC 7.6 in sept of 2012. We are not using HR-compliant Haskell and haven't been for a decade.
@emilypi You've got my go-ahead.
Emily,
I don't really understand your response. I was trying to fix this issue, and I have a patch that implements the behavior that I think you are requesting. I was hoping to get an example that I could include with the patch to document why we're changing long-standing behavior. This would also help me to confirm that the change I've made correctly resolves your issue. Since I couldn't think of a concrete example based on your previous explanation, I was hoping that you could provide one or provide some other clarification. I had thought that this was the right place to ask for clarification before I wrap up my patch and open a PR. What am I missing?
@stevehartdata sorry for interrupting and maybe Emily will have to add something, but the particular issue with newtypes being not equivalent to one-field datatypes is somewhat a folklore now. Some explanation is given on the wiki: https://wiki.haskell.org/Newtype#The_messy_bits there are probably others over the internet.