From Golang: package versioning

Read this post.

Since you didn’t actually read it :stuck_out_tongue:, essentially it says that in large projects where multiple packages depend on a commonly-used utility package, you can’t expect all dependencies to upgrade at once. There have to be different versions of the utility in the same linked binary. And to do this, they include the version in the import statement.

While I wouldn’t go so far as suggesting a change in import syntax, have people thought about the problem of conflicting dependencies in Elm? … More broadly, there’s a lot of experience with package managers wrapped up in the blog post, and I want to share it and make sure we’re learning from other communities’ work.

5 Likes

The elm package manager already handles this, doesn’t it? Packages are stored in elm-stuff as user/package/version/, which allows multiple versions of the same package to be resolved if necessary.

I did actually run into a peerDependency problem – I’m using mdl-context with elm-mdl. mdl-context technically has a peer dependency of some version of elm-mdl, but the package manager has no way to enforce this, so at first I installed a version of mdl-context that was incompatible with my version of elm-mdl.

allows multiple versions of the same package to be resolved if necessary.

Is the same type imported from different versions of a package two nominally distinct types?

Elm currently flattens dependencies: if you try to use two packages that require an incompatible version of a third package, you’ll get an error.

As such, my recommendation for library authors is to have broad version ranges for dependencies, limit external dependencies where possible, and try not to fall too far behind new versions.

2 Likes

Rust (and its package manager, Cargo) handle this problem fairly well. It’ll try to resolve shared dependencies to a single version, but if it can’t, then it’s perfectly fine to have multiple versions of a package in your dependency tree. This is very common for things like the “itertools” library - a collection of small functions that will rarely, if ever, get exposed in your public API.

Types from different versions of the same package are considered distinct (even if their definitions are the same), so if you try to pass a Foo from version 1.0.0 to a function expecting a Foo from version 2.0.0, then you’ll get a somewhat confusing “Expected Foo but got Foo” error.

There’s a clever solution called the “semver trick”, where you publish a package that depends on an older version of itself as a means of getting around this problem. I’m not sure whether that trick applies to Elm though.

3 Likes

Sorry if this is a dumb question, but how does this work in the linker? Do the symbols include the version of the package they are from?

In this case you get a compiler error, so it never makes it to the linking step. The error message hints that you might be using two different versions of the same library.

Generated symbols do include a hash though, so if you looked a library you might see that it has multiple versions of the same functions and types.

1 Like

Clojure as a dynamic language has a very unique approach in this field. When they introduced spec they were able to circumvent the whole numbering-versions thingy entirely. It goes to the point where the interpreter doesn’t care about which exact version of a library is being used as long as the spec of the function you want to use matches. As always Rich Hickey has a delightful talk about that, graphs included :slight_smile:

EDIT: another useful link on the topic for clojure

1 Like

There’s a wonderful podcast about package management called The Manifest that would probably be of interest to people in this thread.

I had a similar experience when upgrading my ChristophP/elm-i18next package. I was faced with picking a version of elm/http which users would have to use for it.
I made an issue about this topic recently https://github.com/elm/compiler/issues/1871 . I think not allowing multiple versions of a package imposing very tight constraints that are sometimes impossible to satisfy. Allowing it would make things a lot easier in the ecosystem especially if there’s this one dependency that has a breaking change and is used everywhere.

1 Like