-
Notifications
You must be signed in to change notification settings - Fork 697
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Private dependencies #4035
Comments
1, 2: I'll clarify thanks. 3, 4: is the encapsulation problem a problem? It is true that via type classes downstream can rely on types being _un_equal, but an easy solution is just ban reexports after all. I wavered on this front, but I think not doing what you say for 4 is better. Consider if B has a public dep on Q, and the new dep D depends on Q instead of B. Then you have the same problem but one level deeper---Q from B->C->Q was unconstrained until C->D->Q makes Q a public dep. If you recursively always freshen, multiple "copies" of a package being in scope being fine, you end up endless Maybe packages get two types of private deps ("this dep should never unify with anything" and "unify with my personal public deps")? |
Maybe "private dependencies" is the wrong way to go about thinking about this. What we want is a form of sealing, where we declare a dependency p to be sealed in some package, and this says, "If you depend on this package, it will not contribute any global constraints on p." So let's take @kosmikus's encapsulation example. In this case, it is not that C has a private dependency on B, but that C wants to seal B. If C depends on D, which in turn depends on B, we cannot seal B without also sealing D (since any install plan that mentions D must also mention B). This avoids us from having to freshen infinitely, because we only freshen the reverse dependency closure of sealed packages up to our current one. The downside of this approach is that if you seal a dependency, you might end up sealing some other dependencies that you wanted to be left unsealed, because those in turn depend on the sealed dependency. Is this an improvement on your "we want the public dependencies of immediate private dependencies to agree"? I still admit to not fully understanding your formula. |
I don't think this is sound? D->B, C->D, and A->C are all public edges, and thus types can be leaked. C can compatibly use D's types (compatible with when it didn't use D at all) in its public interface provided its used in only in new functionality. I do like the sealing language--I'm trying to reconcile it with the above. |
It's not unsound; I'm simply saying that if you wanted to use D "privately", you MUST also use C privately (if C depends on D.) I think this makes sense actually, because in @kosmikus's proposal, if C did depend on D that resulted in D becoming public again. That seems very unexpected; the private dep is effectively ignored. |
Hmm I am reading this again 3 years later and I'm not sure what I was saying before, but I definitely agree with @kosmikus as I think does my old WIP proposal as written.
|
@grayjay I wrote down some thoughts about the design. https://hedgedoc.well-typed.com/PgXXnkI8TSqw2kioBD_FnQ# I would appreciate if you could have a look, the part I am stuck on is how to explain this closure property to the solver. It seems quite difficult to me. Perhaps you could easily see how to achieve it in one of the post processing steps after the solver tree is built. |
@mpickering Thanks for sharing the design. I have a few thoughts so far:
|
|
I numbered my bullet points to make them easier to discuss. 1 . Yes, my example was meant to be circular. The solver allows a package to depend on another instance of itself with a different qualifier. I initially tried to find a non-circular example, but then I realized that the closure property may prevent a package from appearing in both a scope and the dependencies of that scope. I also meant to say that I think that the scopes should be logically transitive, regardless of how they are actually implemented in the solver. 3 . b. In my example, I forgot to mention that the package defining scope The issue is that there is a way to say that 4 . I haven't had a chance to think about the implementation more, but if performance isn't an issue, it is always possible to apply any validation to the final install plan, which is stored in EDIT: I just realized that it is not enough to look at the |
I find it odd to introduce a way to rename modules from a package that is completely different from the one we already have for mixins. More generally, the particular sub-goal of re-presenting a private dependency so as to not clash with a non-private dependency seems like something we could maybe do with the machinery of Backpack? (Very vague idea, sorry, perhaps people who know more can make something of it) |
You raise an interesting point @michaelpj. Indeed, private scopes must definitely not clash with non-private dependencies and perhaps having the unit-id "change-by-instantiation" Backpack mechanism as a means to do that is not a bad idea. As for mixins: I think mixins are using the more general GHC feature of "module renaming" which private scopes also use to rename all the modules of packages within the same scope. You can rename modules (simply) or for instantiations via |
A small update on the implementation: we have made great progress and the feature is both working and is quite well tested. It is lacking documentation and support for other commands like As mentioned above, we wrote some notes on the design here: https://hedgedoc.well-typed.com/PgXXnkI8TSqw2kioBD_FnQ# |
There are a few features that I'd like to be present (or at least planned for) in a complete implementation of private dependencies:
|
Thanks @fgaz. What do you mean by this?
The packages in a private scope will be solved qualified (i.e. we can choose versions for packages in a private scope that are different from the versions picked for the same package in the public (normal) scope). |
I mean solving everything unqualified, treating all |
I guess my point was more about the UX. As a user I see that there is syntax for module renaming in
As written, this would require people to specify all the renamings that they need manually, which is somewhat painful, but more consistent. A completely different idea: since the point of the private dependencies is that they are like a "different package", what if we renamed the package?
A nice feature of this is that it could be implemented as a completely orthogonal feature. That is, we could allow I think this is kind of elegant? |
I don't think this would really work very well in general, you are going to fail to build packages where the constraints in different scopes conflict. As things stand today there are already qualified goals introduced by setup-depends and build-tool-depends which would also be collapsed under this scheme.. that doesn't seem to be an issue currently. I agree it is important to consider how this would affect packagers and people using the ./Setup interface though. |
The alias idea doesn't work very well with the requirement that a scope can contain multiple packages. It's better to group these in a cabal file under a common name/prefix so that they are uniformly available to the user rather than each under an ad-hoc alias. The idea of using
At this point though it wouldn't be too much effort to try different ways of distinguishing different packages so we could try other things. |
If anyone is interested, here is an example of what using private dependencies with the current patch looks like for testing multiple versions of Testing: https://gist.github.com/mpickering/db097bcc7c0e328e99c31480a8e33302 |
While I was reviewing #9743, I thought of a better example to demonstrate my first point in #4035 (comment) and #4035 (comment) above (why it might be better to make private scopes transitive and put transitive dependencies into two overlapping scopes, the original scope and the private scope). Say there is a library that uses containers in its public API ( If I understand correctly, there is an issue when another package ( |
@grayjay My intuition is that the current design should reject your counterexample because it violates the closure property:
In your example, You raise a good point though: does the implementation account for private-dependency closures too, or just for top-level dependency closures? I still believe that the shallow private dependencies are the "right design". Having transitive private scopes would mean my-library could not use |
I was wondering about that before, but @mpickering said that the closure property only applies to public dependencies in #4035 (comment). I thought that it would be better for the closure property to not apply to private dependencies, because private dependencies should be hidden and only affect the package declaring them.
I meant that private dependencies' dependencies would be in both scopes, to force consistent dependencies and allow sharing of lower level libraries. For example, I made diagrams showing how overlapping scopes would work for my example, with and without I feel like the issue is that putting two dependencies in the same private scope (as done by |
@alt-romes I thought about the idea of packages in a private scope working together, and I realized that I don't really understand what happens when packages in a private scope are depended upon by packages in more than one other scope. For example, how is the closure property checked for package A's private scope C0 when A appears in the private scopes of two other packages? In the solver, Package A has two different I'm also not sure how this works when the two sets of packages in C0 overlap. Say one instance of C0 contains only package B, and the other instance contains B and B's dependency C. Then the two instances of C0 require B to depend on C with different qualifiers, depending on whether C is also in the private scope. EDIT: I'm also wondering how the solver would handle linking two scopes when one is a private scope. It seems like the solver could start to link packages but then find that their dependencies are inconsistent because they have different qualifiers. I think that that is a new type of failure. It could lead to a lot of backtracking, because the solver would have to undo all of the linking between the two scopes. It could also decrease the overall amount of linking. |
CC @Ericson2314
I realized we didn't have a tracking ticket for private dependencies, so here it is.
Prior art by @kosmikus : https://wiki.haskell.org/HaskellImplementorsWorkshop/2011/Loeh (when the modular solver was originally introduced; slides have discussion of some tricky cases.)
Newest WIP proposal from @Ericson2314 https://github.com/Ericson2314/ghc-proposals/blob/private-deps/proposals/0000-private-dependencies.rst
I have some comments for @Ericson2314:
IPub*
(I assume * means transitive closure),r IPub
(cast relation into set)setup-depends
andbuild-tools
be solved separately from the public dependencies.The text was updated successfully, but these errors were encountered: