Breaking changes in time 1.15 / GHC 9.14

time 1.15, which ships with GHC 9.14, introduces three new pattern constructors, exported from Data.Time and Data.Time.Clock: Hours, Minutes and Seconds.

This makes it hard to maintain source compatability across GHC 9.12 (or other prior versions) and 9.14, as these new constructors introduce an ambiguity error if the same names have already been defined locally (which we have at work).

In interest of stability, I think new public exports should be confined to new modules, so they are opt-in only (especially for something that’s really a convenience, not a bug fix or new feature).

I’ve posted as much on in the Haskell Foundation’s stability working group repo but wanted to give it some visibilty here too.

The fix isn’t super hard but this is the kind of papercut that makes upgrading to a new GHC difficult.

Could this issue have been prevented by explicit import lists?
I recall a similar issue with Data.Text exporting a new function called show. If you didn’t have an import list, that new export caused problems.

In general, I would strongly recommend explicit import lists instead of restricting new exports

9 Likes

I don’t think it’s practical long term. A package like bytestring or text would end up with dozens of modules and no coherent interface anywhere.


You don’t have to upgrade to time-1.15 when switching to GHC 9.14 though, time-1.14 seems to work perfectly fine with it.

2 Likes

This seems right to me. I always import modules qualified or with explicit export list, with two possible exceptions:

  • The custom prelude in use, if any; or
  • Control.Lens.Operators, if it’s palatable to the team working on that package

Similarly, I’ve found designing modules to be imported qualified makes it much harder to run into the problems that OP has. While it is probably not appropriate to do that for the base and bundled libraries that we have now, it has made it less likely that we hit name clashes during package updates.


I think one of the best things about the Haskell community is the willingness to carefully rework things to get them right. Applicative-Monad Proposal and Foldable-Traversable proposals all took a long time and really improved things. The GHC team also handled the breakage caused by simplified subsumption very well. I think OP’s proposed scheme would run counter to this, and that Bodigrim’s concerns about interface incoherence are well-founded.

4 Likes

Golang defaults to this for good reason. That language has a lot of problems, but it is obsessed with easy upgrades and compat. Qualified import by default is an easy sensible default. It’s a shame Haskell makes it so complicated. Someday we’ll figure out how to “export qualified” and invert control over namespaces.

I’ve thought about this before a tad: A quick half-measure that doesn’t invert control but does make wrangling the existing problem a bit easier might be allowing renaming of things in the import spec, eg:

import Data.Foo (foo)
import Data.Bar (foo as bar)

This really only helps non-qualified imports, but part of the current issue is that they are so prolific. Being able to map names in the import spec might help smooth things over.


Edit: It works with hiding too, where any hidden declaration followed by an as renamed expression would instead be renamed and re-exported:

import Prelude hiding (foo as bar, these, stay, hidden)

I think this would be possible to implement with minimal disruption too, as the syntax should be unambiguous.

1 Like