In this post I’m going to talk about dependencies of a package, so from an application’ point of view these would be transitive or indirect dependencies.
It seems to me, that there are two kinds of dependencies a package can have. Dependencies are:
- Implementation dependencies. Types from these dependencies never occur in exposed signatures of the package.
- Exposed dependencies: Types from these occur in exposed signatures of the package.
I think there is value in separating these two kinds of dependencies explicitly.
For instance, implementation dependencies could be inlined into the package and there would be no observable difference (from the point of the code itself, otherwise there are two: when running
elm install we wouldn’t be prompted to install the transitive dependency and the code size might be slightly larger if the dependency is shared by some other package).
This is not the case for exposed dependencies, since the types used in them need to agree with other uses of those same types.
module Example exposing (decoder) import Color import Json.Decode as Decode exposing (Decoder) decoder : Decoder String decoder = Decode.succeed (Color.toCssString (Color.rgb 0.3 0.5 0.8))
In this example
elm/json would be an Exposed Dependency, while
avh4/elm-color would be an implementation dependency.
Package management: the current behavior needs packages to agree on versions of transitive dependencies. This is reasonable for Exposed Dependencies, as there has to be a consistent vocabulary of what things mean in the final application code. However, it is much less reasonable for Implementation Dependencies, as these are really implementation details of libraries. Hence, if we as an ecosystem get worried about the problem of mismatched dependencies and just copy all code our libraries need into them (i.e. the dependency tree stays completely flat), we end up in a worse place then if the package manager just occasionally duplicates incompatible Implementation Dependencies. In other words, we can think of the cases where two libraries share a compatible version of an Implementation Dependency as just another lucky case of dead code optimization as we end up with only one copy of the code.
Abstraction leakage: Currently implementation details leak in the installation process as Implementation Dependencies are printed out to the user, recorded in json and forced to be matched. The user shouldn’t need to care about Implementation Dependencies.
Design: I think understanding if you are designing a package intended to be an implementation vs exposed dependency is valuable. Some exposed dependency packages for example can serve as a communication layer between many packages in a single domain. As such, they should be designed to be more stable re breaking changes, etc.
Documentation: If you are using exposed dependencies, the current package website does not help you. It does not link to the documentation of those types and since there can be many packages with types matching that name, the user may struggle to figure out which one these refer to. As a workaround, I would suggest mentioning and linking all exposed dependencies in your readme.
I hope you find these insights as interesting as I do. But what I wonder about is, should these concepts be made explicit in our tooling as I suggest above?