Skip to content

Support containers-0.8. #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 20, 2025

Conversation

jonathanknowles
Copy link
Contributor

@jonathanknowles jonathanknowles commented Mar 2, 2025

This PR adds support for containers version 0.8.

It:

  1. Removes the custom implementation of Data.IntMap.NonEmpty.Internal.traverseMapWithKey and updates call sites to use Data.IntMap.traverseWithKey instead, which correctly traverses in ascending order of keys for containers >= 0.6.3.1 (see below.)

  2. Adjusts Data.Set.NonEmpty.combineEq so that:

    • for containers < 0.8: combineEq chooses the first occurrence of each duplicated element (consistent with behaviour of containers < 0.8).
    • for containers >= 0.8: combineEq chooses the last occurrence of each duplicated element (consistent with behaviour of containers >= 0.8).

    This is to account for a change in behaviour of Data.Set.from{Asc,Desc}List between containers-0.7 and containers-0.8:

    • for containers < 0.8: Set.from{Asc,Desc}List chooses the first occurrence of any duplicated element.
    • for containers >= 0.8: Set.from{Asc,Desc}List chooses the last occurrence of any duplicated element.
  3. Updates both lower and upper bounds for containers to reflect the set of versions for which all tests pass (containers >= 0.6.3.1 && < 0.9)

  4. Removes the compatibility layer for containers < 0.5.11. Assuming the package is only buildable for containers >= 0.6.3.1, then it should be safe to remove this layer.

Testing

I have confirmed that the test suite passes for all versions of containers between 0.6.3.1 and 0.8 (inclusively):

  • 0.8
  • 0.7
  • 0.6.8
  • 0.6.7
  • 0.6.6
  • 0.6.5.1
  • 0.6.4.1
  • 0.6.3.1 (released in 2020-07-14)

The test suite fails for the following older versions of containers:

  • 💥 0.6.2.1 (released in 2019-06-24)
  • 💥 0.6.1.1
  • 💥 0.6.0.1
  • 💥 0.5.11.0

Tests that fail with older versions of containers:

  • Tests.IntMap.mapAccumWithKey
  • Tests.IntMap.sequenceA
  • Tests.IntMap.sequence1
  • Tests.IntMap.traverseWithKey
  • Tests.IntMap.traverseWithKey1

@jonathanknowles jonathanknowles force-pushed the support-containers-0.8 branch from 3de17be to 747201b Compare March 2, 2025 08:09
@jonathanknowles jonathanknowles marked this pull request as draft March 2, 2025 08:10
@jonathanknowles jonathanknowles marked this pull request as ready for review March 2, 2025 09:49
Comment on lines 716 to 717
-- | /O(n)/. A fixed version of 'Data.IntMap.traverseWithKey' that
-- traverses items in ascending order of keys.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Just passing by)
I know this is not from this PR, but this does not seem right. IntMap traverses in ascending order of keys, this implementation doesn't.

λ> import qualified Data.IntMap.Lazy as IM
λ> import qualified Data.IntMap.NonEmpty as NEIM
λ> () <$ IM.traverseWithKey (curry print) (IM.fromList [(i,i) | i <- [-3..2]])
(-3,-3)
(-2,-2)
(-1,-1)
(0,0)
(1,1)
(2,2)
λ> () <$ NEIM.traverseWithKey (curry print) (NEIM.unsafeFromMap (IM.fromList [(i,i) | i <- [-3..2]]))
(-3,-3)
(-1,-1)
(-2,-2)
(2,2)
(1,1)
(0,0)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Just passing by) I know this is not from this PR, but this does not seem right. IntMap traverses in ascending order of keys, this implementation doesn't.

Hi @meooow25, many thanks for commenting, I really appreciate your analysis.

Could it be that in the past, these traversal functions were not always so well-behaved?

For example, as recently as containers-0.6.2.1, we can see a discrepancy between Data.IntMap and Data.IntMap.Strict in the behaviour of traverseWithKey (borrowing your example code):

$ cabal repl --constraint=containers==0.6.2.1
> import qualified Data.IntMap as IM
> import qualified Data.IntMap.Strict as IMS
> () <$ IM.traverseWithKey (curry print) (IM.fromList [(i,i) | i <- [-3..2]])
(-3,-3)
(-2,-2)
(-1,-1)
(0,0)
(1,1)
(2,2)
> () <$ IMS.traverseWithKey (curry print) (IM.fromList [(i,i) | i <- [-3..2]])
(0,0)
(1,1)
(2,2)
(-3,-3)
(-2,-2)
(-1,-1)

But with containers-0.6.3.1, this discrepancy is fixed:

$ cabal repl --constraint=containers==0.6.3.1
> import qualified Data.IntMap as IM
> import qualified Data.IntMap.Strict as IMS
> () <$ IM.traverseWithKey (curry print) (IM.fromList [(i,i) | i <- [-3..2]])
(-3,-3)
(-2,-2)
(-1,-1)
(0,0)
(1,1)
(2,2)
> () <$ IMS.traverseWithKey (curry print) (IM.fromList [(i,i) | i <- [-3..2]])
(-3,-3)
(-2,-2)
(-1,-1)
(0,0)
(1,1)
(2,2)

Though in any case, I think it's clear that the current implementation of traverseMapWithKey does not work as expected. Many thanks for pointing this out! I will run some more tests and revise this PR.

Copy link
Contributor Author

@jonathanknowles jonathanknowles Mar 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated this PR to remove the custom implementation of Data.IntMap.NonEmpty.Internal.traverseMapWithKey, updating all call sites to use Data.IntMap.traverseWithKey instead.

According to my testing, the test suite now passes for all the following versions of containers:

  • 0.6.3.1
  • 0.6.4.1
  • 0.6.5.1
  • 0.6.6
  • 0.6.7
  • 0.6.8
  • 0.7
  • 0.8

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But with containers-0.6.3.1, this discrepancy is fixed:

Good find, the code here is likely an (incorrect) attempt to work around the containers issue.

I also think that bumping to a fixed version of containers is the best thing to do here, and one can avoid dealing with the internals 👍

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for reviewing -- your assement was correct, this was odd behavior in containers that was actually caught and updated by this package's property tests haskell/containers#579

This commit provides a workaround for a change in the behaviour of
`Set.from{Asc,Desc}List` w.r.t. duplicate elements, when upgrading
from `containers-0.7` to `containers-0.8`.

We can use the `K` type from `Tests.Util` to demonstrate this.

With `containers-0.7`, `Set.from{Asc,Desc}List` chooses the **_first_**
occurrence of any duplicated element:

```hs
$ cabal repl nonempty-containers-test --constraint=containers==0.7
> import qualified Data.Set as Set
> import Tests.Util
> Set.fromAscList [K 'a' 1, K 'a' 2]
fromList [K {getKX = 'a', getKY = 1}]
> Set.fromDescList [K 'a' 1, K 'a' 2]
fromList [K {getKX = 'a', getKY = 1}]
```

With `containers-0.8`, `Set.from{Asc,Desc}List` chooses the **_last_**
occurrence of any duplicated element:

```hs
$ cabal repl nonempty-containers-test --constraint=containers==0.8
> import qualified Data.Set as Set
> import Tests.Util
> Set.fromAscList [K 'a' 1, K 'a' 2]
fromList [K {getKX = 'a', getKY = 2}]
> Set.fromDescList [K 'a' 1, K 'a' 2]
fromList [K {getKX = 'a', getKY = 2}]
```

This commit adjusts the bias of `Data.Set.NonEmpty.combineEq` to match
the bias of `Set.from{Asc,Desc}List` in the version of `containers`
being linked against.
@jonathanknowles jonathanknowles marked this pull request as draft March 3, 2025 04:09
The current definition of `traverseMapWithKey` does not traverse keys in
ascending order:

```hs
> import qualified Data.IntMap as IM
> import qualified Data.IntMap.NonEmpty.Internal as NEIM
> kvs = [(i, i) | i <- [-2 .. 2]]
> () <$ IM.traverseWithKey (curry print) (IM.fromList kvs)
(-2,-2)
(-1,-1)
(0,0)
(1,1)
(2,2)
> () <$ NEIM.traverseMapWithKey (curry print) (IM.fromList kvs)
(-2,-2)
(-1,-1)
(2,2)
(1,1)
(0,0)
```

As a result, traversal functions for `Data.IntMap.NonEmpty` also do not
traverse keys in ascending order.

This commit redefines `traverseMapWithKey` to be a simple synonym of
`Data.IntMap.traverseWithKey`.

With this change, the test suite passes for all versions of containers
from `0.6.3.1` up to `0.8` inclusively:

- `0.6.3.1`
- `0.6.4.1`
- `0.6.5.1`
- `0.6.6`
- `0.6.7`
- `0.6.8`
- `0.7`
- `0.8`
Since `traverseMapWithKey` is now a synonym of `IntMap.traverseWithKey`,
we can inline its definition at all call sites.
@jonathanknowles jonathanknowles force-pushed the support-containers-0.8 branch from 94969c3 to 423cdc6 Compare March 3, 2025 04:28
@jonathanknowles jonathanknowles marked this pull request as ready for review March 3, 2025 04:32
The lower bound of `0.6.3.1` is the earliest version for which all tests
pass. This version of `containers` was released on 2020-07-14.

This commit excludes older versions of `containers` up to and including
version `0.6.2.1`, which was released on 2019-06-24.
If the package itself is only buildable for `containers >= 0.6.3.1`,
then this code is no longer reachable, and thus can be removed.
@jonathanknowles jonathanknowles force-pushed the support-containers-0.8 branch from 2be3cc3 to 2bb5a2a Compare March 4, 2025 05:32
These functions are now just synonyms, so they can be safely inlined.
@jonathanknowles jonathanknowles force-pushed the support-containers-0.8 branch from 2bb5a2a to 459536f Compare March 4, 2025 06:10
@jonathanknowles
Copy link
Contributor Author

Hi @mstksg

Hope it's okay for me to have created this PR. I had some spare time over the weekend, so I took a look at how nonempty-containers could be upgraded to support containers-0.8.

(I use nonempty-containers quite a bit—thanks for providing it!)

If there's anything you'd like me to adjust or if you’d prefer to handle things differently, just let me know—I’d be happy to help if I can.

All the best,
Jonathan

@jonathanknowles
Copy link
Contributor Author

jonathanknowles commented May 19, 2025

Hi @mstksg

Just checking in on this PR. It's been a couple of months since I opened it, so I wasn't sure if it might've been missed, or if things have just been a bit busy on your end.

If there's anything you need from my side, feel free to let me know. Thanks again!

@mstksg
Copy link
Owner

mstksg commented May 19, 2025

Hi jonathan -- yes, I did miss the notification initially. Appreciate this very much, thanks for following up as well! I'll see if I can push this in this week

@mstksg mstksg merged commit 8f4d1d3 into mstksg:master May 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants