Closed bgamari closed 1 month 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.
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.
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.
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.
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.
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)
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?
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.
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.
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.
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
.
Thanks all for this discussion. It has been migrated to https://gitlab.haskell.org/ghc/ghc/-/issues/24835. :)
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:The documentation of
Bar
will claim thatBar.hello
has been available since 0.1; however, this is a version of packagefoo
, notbar
.In cases where
foo
andbar
are maintained together, it may make sense to be able to record the availability ofhello
frombar
in the documentation ofFoo
. For instance, you might want to write: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.