Cannot understand why MonoFoldable for my type doesn't compile, or the error message

90 Views Asked by At

I have the following code:

{-# LANGUAGE NoImplicitPrelude, OverloadedStrings, TypeFamilies #-}

module AI.Analysis.Rules where

import ClassyPrelude

-- Our set of rules

data RuleSet a = RuleSet [Rule a] [Rule a]
  deriving (Eq)

mkRuleSet :: (Ord a) => [Rule a] -> RuleSet a
mkRuleSet rules = uncurry RuleSet (partition isStandard uniques)
  where uniques = ordNub rules
        isStandard x = case x of
          Standard _ _ -> True
          LastResort _ -> False

instance (Show a) => Show (RuleSet a) where
  show (RuleSet s l) = unlines [toLines s, "----", toLines l]
    where toLines = unlines . fmap show

instance (Ord a) => Monoid (RuleSet a) where
  mempty = RuleSet [] []
  mappend (RuleSet s1 l1) (RuleSet s2 l2) = RuleSet (ordNub (s1 ++ s2)) (ordNub (l1 ++ l2))

instance (Ord a) => Semigroup (RuleSet a) where
  (<>) = mappend

type instance Element (RuleSet a) = (Rule a)

instance MonoFoldable (RuleSet a) --this is unhappy

-- A rule in our system
-- For now, we assume rules *individually* are always internally-consistent

data Rule a = Standard [a] a | LastResort a
  deriving (Eq)

mkRule :: (Eq a, Ord a) => [a] -> a -> Rule a
mkRule as c = case as of
  [] -> LastResort c
  _ -> Standard ((sort . ordNub) as) c

-- Last-resort rules and standard rules cannot be compared for consistency
mutuallyConsistent :: (Eq a) => Rule a -> Rule a -> Maybe Bool
mutuallyConsistent (LastResort c1) (LastResort c2) = Just (c1 == c2)
mutuallyConsistent (Standard as1 c1) (Standard as2 c2) = Just ((as1 /= as2) || (c1 == c2))
mutuallyConsistent _ _ = Nothing

instance (Show a) => Show (Rule a) where
  show x = case x of
    Standard as c -> formatAnd as ++ " -> " ++ show c
    LastResort c -> "-> " ++ show c
     where formatAnd = unwords . intersperse "^" . map show . otoList

 -- LastResort rules are always ordered smaller than standard ones
 instance (Ord a) => Ord (Rule a) where
   (<=) (LastResort _) (Standard _ _) = True
   (<=) (Standard _ _) (LastResort _) = False
   (<=) (LastResort c1) (LastResort c2) = c1 <= c2
   (<=) (Standard as1 c1) (Standard as2 c2) = (as1 <= as2) || (c1 <= c2)

However, I get the following error from the compiler, whose meaning I am having trouble understanding:

/home/koz/documents/uni/research/summer-research-2015/clinical/rules-analysis/src/AI/Analysis/Rules.hs:47:10:
    Couldn't match type ‘a’ with ‘Rule a’
      ‘a’ is a rigid type variable bound by
          the instance declaration
          at /home/koz/documents/uni/research/summer-research-2015/clinical/rules-analysis/src/AI/Analysis/Rules.hs:47:10
    Expected type: Element (RuleSet a)
      Actual type: a
    Relevant bindings include
      ofoldMap :: (Element (RuleSet a) -> m) -> RuleSet a -> m
        (bound at /home/koz/documents/uni/research/summer-research-2015/clinical/rules-analysis/src/AI/Analysis/Rules.hs:47:10)
    In the expression:
      mono-traversable-0.10.0.1:Data.MonoTraversable.$gdmofoldMap
    In an equation for ‘ofoldMap’:
        ofoldMap
          = mono-traversable-0.10.0.1:Data.MonoTraversable.$gdmofoldMap
    In the instance declaration for ‘MonoFoldable (RuleSet a)’

Near as I can tell, my thinking seems to make sense - after all, a RuleSet is just a container for Rules, which should allow for foldability, but the error message in question doesn't make any sense to me. Could someone please clarify what I failed to grasp here?

2

There are 2 best solutions below

3
Thomas M. DuBuisson On BEST ANSWER

Have you tried actually implementing the class? It appears there is some oddity with the default definitions and your type family. If you define at least the below then the file type checks:

instance MonoFoldable (RuleSet a) where --this is unhappy
    ofoldl1Ex' = undefined
    ofoldr1Ex  = undefined
    ofoldl'    = undefined
    ofoldr     = undefined
    ofoldMap   = undefined

EDIT: The classy prelude, which I now know I will never use, has default implementations and type signatures that include the constraints t a ~ mono, a ~ Element (t a). Working carefully since I had to think twice here. t a ~ RuleSet a0 so t == RuleSet and a == a0. Then a ~ Element (RuleSet a), which is your exact error in the message, would suggest a ~ Rule a and that just isn't right.

0
Michael Snoyman On

To clarify about the default implementations: since there are a large number of types out there which are properly polymorphic - and therefore instances of Functor - MonoFunctor provides an easy way to make those also instances of MonoFunctor, via default method signatures. In the case where you have a Functor, simply declaring instance MonoFunctor is sufficient.

In your case, you get a confusing error message since your type is actually a Functor, but for a different type than your desired MonoFunctor instance. Specifically, by its shape, RuleSet a is a Functor for a, while you want it to be Rule a. There's nothing wrong with that, it just conflicts with the default implementations, and therefore you need to provide separate implementations.

Note that this isn't specific to your type: anything which isn't a simple translation from Functor to MonoFunctor requires this work. This applies to some of the built-in instances, like Text and ByteString.