haskellhaskell-vector

Is there a way to avoid UndecidableInstances in this example?


When I try this:

import GHC.Generics (Generic)
import Control.DeepSeq (NFData(..))
import Data.Vector.Generic (Vector)

data Entry a = Entry !Bool a
             deriving (Generic, NFData)

-- The variable @v@ is meant to be instantiated with a 'Vector'
-- type.  Most operations for the type have a @Vector v (Entry a)@
-- constraint.
newtype DenseIntMap v a = DenseIntMap (v (Entry a))

instance NFData (v (Entry a)) => NFData (DenseIntMap v a) where
  rnf (DenseIntMap va) = rnf va

...I get this error:

/Users/casillas/GitHub/tau-sigma/src/TauSigma/Util/DenseIntMap.hs:53:10:
    Constraint is no smaller than the instance head
      in the constraint: Vector v (Entry a)
    (Use UndecidableInstances to permit this)
    In the instance declaration for ‘NFData (DenseIntMap v a)’

/Users/casillas/GitHub/tau-sigma/src/TauSigma/Util/DenseIntMap.hs:53:10:
    Constraint is no smaller than the instance head
      in the constraint: NFData (v (Entry a))
    (Use UndecidableInstances to permit this)
    In the instance declaration for ‘NFData (DenseIntMap v a)’

Using UndecidableInstances indeed makes it go away, but I'm wary of using that extension. Is there some other way to make things work in this case? (Without changing the types too much, preferably.)


Solution

  • Warning: I haven't tested any of this code.

    The approach that seems cleanest to me is to follow a Prelude.Extras-style path:

    class NFData1 f where
      rnf1 :: NFData a => f a -> ()
    

    You can now write, for each vector type, something like

    instance NFData1 V where
      rnf1 = rnf
    

    And then

    instance (NFData1 v, NFData a) => NFData (DenseIntMap v a) where ...
    

    An alternative approach that may fit your current code better is to think about v as a Vector explicitly. Instead of worrying about how v a would prefer to force itself, ram your own notion down its throat by folding: something like

    instance (Vector v a, NFData a) => NFData (DenseIntMap v a) where
      rnf = V.foldl' (\() e -> rnf e) ()
    

    This second approach seems likely to play poorly with vector fusion unless you're careful about which vectors you want to force from left to right and which from right to left.