[Haskell-cafe] ANN: network

Niklas Hamb├╝chen mail at nh2.me
Sat Jan 19 17:38:09 UTC 2019

Thanks for the quick reply!

> I'd love to hear ideas.

I recommend the following way:

* Never change the type of an existing function unless absolutely necessary.
* Do not change the the behaviour/semantics of a function unless it's an obvious fix/improvement.
  (In other words, if somebody may in some reasonable way rely on the old behaviour, don't change it).
* Improve functions by introducing new functions with names, marking the old functions as deprecated.
* GHC will show deprecation warnings to users.
** In the deprecation message, point out the intended replacement.
** If a deprecated function is planned to be removed in the next release, say it in the message.
* Consider removing deprecated functions after some years. But only if e.g. maintainability demands it.
** In some cases, consider moving the functions to an `.Old` module or similar.

Key point:
* Use deprecations and new functions liberally to make progress. Almost never break existing functions.

I like to call this approach "the Java way of deprecation", and it helps an ecosystem to move forward swiftly while keeping people happy a whole lot. This is because it allows incremental transitions instead of hard cutoff points that often don't align with people's schedules or test plans.

How this relates to the PVP:
This thought process happens *before* PVP considerations enter the stage.
After you've made your changes (hopefully as few breaking ones as possible), you can use the PVP to determine what the new version should be.
The reverse logic should not be applied: If some change made demands a major version bump (e.g. removal of an old deprecated function that nobody uses), that does not "allow" other functions to be removed more liberally. The rationale is that our goal is not to do as much as a given version jump allows, but to minimise breaking changes for users even when we know that some breaking changes must be made.

(Also, while the PVP as mentioned doesn't tell you what to do with your package contents and applies afterwards, the diagram in https://pvp.haskell.org/pvp-decision-tree.svg mentions "Consider renaming the function instead" next to "Did the behaviour of any exported functions change", so I think what I recommend is in the spirit of those that came up with the process).

Concrete example:

This is how I imagine applying the above to network would have worked:

* Deprecate `send`, `sendTo`, `recv`, etc. (As correctly done in network-2.7)
* Do NOT remove them. Leave at least 2 years time before touching them.
* In this case, move them to Network.Socket.String, instead of removing them. For 2 reasons:
** These functions have been this ways since forever and lots of code will use them. Putting them into a legacy module will allow projects to trivially get into a compiling state again with one `import` change, vs having to fix every use site. This eases migration.
** While certainly not a good idea, the String based functions aren't so fatally flawed that they have no justification for further existence, so keeping them in a legacy module would do no harm.
* Introduce `socketToFd :: Socket -> IO CInt`
* Deprecate `fdSocket`, saying why it's bad and that `socketToFd` is the replacement
* Keep `fdSocket` forever

(Just to be clear, I think the `String` based network API is bug; a reasonable and modern programming ecosystem shouldn't have that, and this should be addressed. So I'm not recommending this approach because I think these functions are great, but because I think this is how to transition away from design bugs in general.)

Then, looking at the above changes, if we follow PVP, we'd look at the PVP decision tree, and determine what the new version should be that way. If (after a long time period) we'd do the "move to Network.Socket.String", it would tell us that a major bump is necessary.
Of course one can also introduce a major version bump when PVP says that a minor bump would be sufficient if one wants to "mark an epoch change" -- that is at the liberty of the maintainer.


I found this approach to work very well and am convinced of it.
But I'm happy to hear other people's opinion about it to see if it's really as agreeable as I think.
If yes, I'd be happy to write down these deprecation guidelines in some repo so that projects can refer to them and say "we follow that general approach" (similar to the PVP FAQ link).

What do you think?


More information about the Haskell-Cafe mailing list