haskell / haddock

Haskell Documentation Tool
www.haskell.org/haddock/
BSD 2-Clause "Simplified" License
361 stars 242 forks source link

Package-qualified `@since` annotations #1630

Closed bgamari closed 1 month ago

bgamari commented 5 months ago

Currently the @since annotation is used to communicate the version in which a declaration was introduced in a package. However, there is currently no story for the introduction of declarations by re-exports. For instance, imagine that we have:

-- in package foo
module Foo (hello) where
-- | @since 0.1
hello :: IO ()
...

-- in package bar
module Bar (hello) where
import Foo (hello)

The documentation of Bar will claim that Bar.hello has been available since 0.1; however, this is a version of package foo, not bar.

In cases where foo and bar are maintained together, it may make sense to be able to record the availability of hello from bar in the documentation of Foo. For instance, you might want to write:

-- in package foo
module Foo (hello) where
-- |
-- @since foo-0.1
-- @since bar-0.2
hello :: IO ()
...

Clearly this isn't suitable in all cases; encoding information about the exports of one package (bar) in another (foo) is certainly not ideal. However, it is quite useful in cases where an declarations of a internal package are re-exported by a user-facing package.

approach does have the advantage of being a simple and easily implemented generalization of the existing mechanism.

Another more general, yet harder to realize, approach can be found in #1629.

Kleidukos commented 5 months ago

There is certainly something to be said about generalising the notion of versioning exports.

My position (unless I'm presented with a very good model later) is that we start small and consider introducing a @export-since tag in the entity's haddock, living alongside of @since. I'm not against expanding but it's better to be restrictive at first and expanding than being too vague/general and having to tighten things up later.

adamgundry commented 5 months ago

Being able to write @since bar-0.2 on the declaration itself seems like a good idea to me (even if we add @since annotations on re-exports as well). The meaning is obvious, and for closely-related packages developed together, it would be easier to put the annotations together rather than splitting them up between the definition site and re-export site. Moreover, it may be actively desirable for the Haddocks for the defining package to mention the availability in the re-exporting package (which would not happen if the annotation was only on the re-export).

For example, in optics we re-export many modules from optics-extra or optics-core. Ideally the @since pragmas for optics-core would be able to mention the optics version as well as the optics-core version, perhaps something like @since optics-core-0.5.1, optics-0.5.2. Then a user looking at the Haddocks for optics-core would see all the information at a glance, whereas @since 0.5.1 is less informative and requires them to figure out which package's Haddocks they are currently browsing.

chreekat commented 5 months ago

I think I need to take the other side here. Allowing some package A to state facts about another package B—facts which can't be verified by the closure of A and its dependencies alone—creates bad coupling.

adamgundry commented 5 months ago

Allowing some package A to state facts about another package B—facts which can't be verified by the closure of A and its dependencies alone—creates bad coupling.

I agree that this is suitable only with closely-coupled packages. But those close couplings exist anyway (the optics family is one example, base/ghc-internal is another). So it seems a shame not to be able to have cross-references in the documentation.

int-index commented 5 months ago

But those close couplings exist anyway

I don't see it as a reason to solidify this close coupling in yet another way. We should keep it to a minimum rather than embrace it. Close coupling is a necessary evil sometimes, but it's not necessary at all in this case if we simply add @since annotations on exports/reexports proposed in #1629.

Bodigrim commented 5 months ago

Even for the motivating case of the proposal (which is ghc-internal + base) such coupling will become harmful quickly. The purpose of base split was to allow independent coevolution, but if ghc-internal says @since base-4.16 @since ghc-internal-0.1 and a new version of base hides an internal entity someone has to update ghc-internal as well. And if base becomes reinstallable, it becomes even impossible to specify correct annotations on the side of ghc-internal.

Another issue would be if a third-party library (say, relude) re-exports an identifier from ghc-internal. Suddenly haddocks for relude mention which version of base exports it, but cannot be amended to say which version of relude does. Should ghc-internal provide @since annotations for each and every package re-exporting its content?


I think that "package-qualified" is a red herring here. Imagine that the same definition is exported from different modules of the same package. Say, it was originally exported from Data.Foo.Internal in foo-0.1, but rose in prominence and by foo-0.2 we want to export it from Data.Foo as well. Or vice versa. How can we annotate its availability in a faithful way? (This is actually a much more common problem in practice than package splits)

1629 solves both problems in the same fashion and is a much more principled approach.

adamgundry commented 5 months ago

Fair points. I can see that for ghc-internal/base we will ultimately want #1629 instead.

One remaining issue #1629 does not cover is reexported-modules (which we use in optics). Would we expect to be able to annotate those somehow, e.g. with comments in the .cabal file?

Bodigrim commented 5 months ago

FWIW at the moment Haddock entirely ignores reexported-modules (for instance, https://hackage.haskell.org/package/optics is almost empty), so this would have to be resolved first.

bgamari commented 4 months ago

One case where this approach will likely be necessary is that of typeclass instances. Afterall, instances, unlike other declarations, cannot be named in export lists, rendering the #1629 inapplicable. However, I don't believe that this will be problematic since instances can not be hidden, rendering the @Bodigrim's concern above moot so long as there is roughly a one-to-one correspondence between internal package versions and those of the user-facing package.

chreekat commented 4 months ago

I wonder if one could simply create a grid table in the long-form module description of the downstream module. Any tightly-coupled packages that would benefit from this feature will likely have centralized release infrastructure, anyway. A tool to automate the process could be incorporated there.

TeofilC commented 2 months ago

Another example of a use-case (similar but slightly distinct) is: https://gitlab.haskell.org/ghc/ghc/-/merge_requests/12479#note_563033

Here I am moving much of the internals of template-haskell into ghc-internal. This includes Lift instances. So they now live in ghc-internal but they used to live in template-haskell. Some other instances are being moved to base.

In these cases it would be helpful to be able to say both when they were first introduced to template-haskell and when they were added to base/ghc-internal, which are dependencies of template-haskell.

Kleidukos commented 1 month ago

Thanks all for this discussion. It has been migrated to https://gitlab.haskell.org/ghc/ghc/-/issues/24835. :)