Skip to content

Question: Is forcing this value in WHNF avoidable?

Summary

In containers we have

data Map k a = Bin !Int !k a !(Map k a) !(Map k a)
             | Tip

And we can find the minimum value in the map as follows.

data KV k a = KV !k a

lookupMin :: Map k a -> Maybe (KV k a)
lookupMin Tip = Nothing
lookupMin (Bin _ k x l _) = Just $! lookupMinSure k x l

lookupMinSure :: k -> a -> Map k a -> KV k a
lookupMinSure k x Tip = KV k x
lookupMinSure _ _ (Bin _ k x l _) = lookupMinSure k x l

This leads to this core for lookupMinSure.

Rec {
-- RHS size: {terms: 19, types: 26, coercions: 0, joins: 0/0}
SetMin.$wlookupMinSure [InlPrag=[2], Occ=LoopBreaker]
  :: forall {k} {a}. k -> a -> Map k a -> (# k, a #)
[GblId[StrictWorker([~, ~, !])],
 Arity=3,
 Str=<ML><L><1L>,
 Unf=OtherCon []]
SetMin.$wlookupMinSure
  = \ (@k) (@a) (k1 :: k) (x :: a) (ds :: Map k a) ->
      case ds of {
        Bin bx k2 x1 l ds1 -> SetMin.$wlookupMinSure @k @a k2 x1 l;
        Tip -> case k1 of conrep { __DEFAULT -> (# conrep, x #) }
      }
end Rec }

Notice that there is a case k1 of conrep which forces the key. This should not be necessary, since the map is strict in the key.

@treeowl suggested trying this alternative:

data UBox :: Type -> UnliftedType where
  UBox :: !a -> UBox a

lookupMin' :: Map k a -> Maybe (KV k a)
lookupMin' Tip = Nothing
lookupMin' (Bin _ k x l _) = Just $! lookupMinSure' (UBox k) x l

lookupMinSure' :: UBox k -> a -> Map k a -> KV k a
lookupMinSure' (UBox k) x Tip = KV k x
lookupMinSure' _ _ (Bin _ k x l _) = lookupMinSure' (UBox k) x l

This generates the core

Rec {
-- RHS size: {terms: 19, types: 26, coercions: 0, joins: 0/0}
SetMin.$wlookupMinSure' [InlPrag=[2], Occ=LoopBreaker]
  :: forall {k} {a}. k -> a -> Map k a -> (# k, a #)
[GblId[StrictWorker([!, ~, !])],
 Arity=3,
 Str=<1L><L><1L>,
 Unf=OtherCon []]
SetMin.$wlookupMinSure'
  = \ (@k) (@a) (ww :: k) (x :: a) (ds :: Map k a) ->
      case ww of ww1 { __DEFAULT ->
      case ds of {
        Bin ipv ipv1 ipv2 ipv3 ipv4 ->
          SetMin.$wlookupMinSure' @k @a ipv1 ipv2 ipv3;
        Tip -> (# ww1, x #)
      }
      }
end Rec }

This still forces the key, but now on every iteration.

Questions:

  • Why does the UBox version force the key on every iteration?
  • Is it possible to implement this in a way that GHC realizes that forcing they key is not necessary?

Original discussion on containers for reference: #976.

Steps to reproduce

Compile the code above.

Environment

  • GHC version used: 9.6.3
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information