Provide a way to break your api without breaking other packages


#1

Here is the basic problem:

Package A1 (A version 1) depends on packages B1 and C1. Both of these depend on D1.

Now, D1 is upgraded to D2. Package B1 is still B1 but changes its usage to D2. Package C1 stays with D1. Now, dependencies are broken without any change in the version for B, since A now depends on both D1 and D2.

Now, the compiler could be changed to allow for multiple versions of the same module in the same application, but this seems like a hard problem to get right. How would module imports work, etc?

I propose a way to upgrade your package API without breaking dependencies by providing a shim. This is a module that has the old API but is implemented using the new API.

So, when A discovers that it depends on both D1 and D2, it consults D2 and finds a shim for D1 that it can inject into C1. C1 still depends on D1 but uses the D1 shim from D2.

It could work something like this: when you bump your package to version 2, you can add a directory called Shim1 which means “shim for version 1”. Inside this directory, you’ll put shims for all modules that have a breaking API change.

So, the compiler would see that the shim exists during dependency analysis and inject something like

import Shim1.Http as Http

into the C1 modules. Even if this is not done automatically by the compiler, it would mean that C1 could easily just depend on D2 by replacing the imports.

This is something that people can do right away, and you can even do it after the fact. The shim module also serves as good documentation for when you want to upgrade to the new version yourself.

I’ve made an example shim for Http1 here (expectStringResponse is not shimmed, because it’s a bit special, but should be possible): https://gist.github.com/norpan/63d74d96a880fb3b1ad3f475365c3746


#2

For proper support of shims, I think that the shims should really be listed in elm.json of the package.

Otherwise, even with standard directory names like Shim1, programs which do dependency analysis would need to check package source code to see if any shims exist.


#3

Of course, it can be done properly, but that’s no reason not to start doing it right away. The module would already be listed in elm.json, since that’s what you do with your package modules, and it would show up on the package web site etc.


#4

True, I forgot that. And exposed-modules already allows labelled lists so you could already list any shims separately.

"exposed-modules": {
    "Shims": [
        "Shim1.Http"
    ],
    ...
},

#5

Of course, for a simple package, the shim could just be the previous version of the package. That would solve the multiple modules problem because they are now two different modules.


#6

I especially like:

The shim module also serves as good documentation for when you want to upgrade to the new version yourself

When I am upgrading, I try to reduce the number of “moving parts”. If I can first upgrade to the new version, and them make the suggested version changes, it makes the upgrade a lot safer and easier.


#7

Wouldn’t a wider reflexion on packages be usefull? For example, how to resolve module conflicts like Foo from bar/baz or Foo from buzz/foo? I’ve had the case only once, but greater the community, more often this will occur.


#8

Yes, it would also be useful to be able to be able to have the same module name in different packages, and then you can, of course, implement something like http_1/Http and http_2/Http for shims. But that requires more changes to the compiler than just adding a new module, which can be done right away.