Skip to content

More flakiness in RULES involving coerce

Summary

Very basic lambdas in RULES flake out in the presence of newtype wrappers.

Steps to reproduce

I wrote a function

map# :: (a -> (# b #)) -> Map k a -> Map k b
{-# NOINLINE [1] map# #-}

to see if I can unify the implementations (and rewrite rules) for strict and lazy Map operations.

L.map, S.map :: (a -> b) -> Map k a -> Map k b
L.map f = map# (\x -> (# f x #))
S.map f = map# (\x -> let !y = f x in (# y #))
{-# INLINABLE L.map #-}
{-# INLINABLE S.map #-}

I added a rule

"mapCoerce" map# (\x -> (# coerce x #))

to see if I could recover L.map coerce = coerce. To my surprise and delight, this worked! But the story wasn't over.

Because containers tries to maintain some level of compatibility with non-GHC compilers (particularly to make it not too hard to port to some future Haskell implementation), I didn't want to use unboxed tuples directly. So I did an obvious thing:

newtype SoloU a = SoloU__ (# a #)
pattern SoloU :: a -> SoloU
pattern SoloU a = SoloU__ (# a #)
{-# INLINE SoloU #-}

mapU :: (a -> SoloU b) -> Map k a -> Map k b
{-# NOINLINE [1] mapU #-}

L.map, S.map :: (a -> b) -> Map k a -> Map k b
L.map f = mapU (\x -> SoloU (f x))
S.map f = mapU (\x -> SoloU $! f x)
{-# INLINABLE L.map #-}
{-# INLINABLE S.map #-}

Unfortunately, I couldn't find a way to get a rule to fire. I tried

mapU (\x -> SoloU__ (# coerce x #)) = coerce
mapU (\x -> coerce (# x #)) = coerce
mapU (coerce (\x -> (# x #)) = coerce

but none of those worked.

Expected behavior

I would expect all three of the rules I tried to work.

Environment

  • GHC version used: 9.4

Optional:

  • Operating System:
  • System Architecture:
Edited by David Feuer
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information