haskellgraphinstancetypeclasstype-signature

Couldn't match type error when implementing type class function


I am trying to build my own simple graph library that I can use for advent of code. I have a type class Graph and one concrete implementation MapGraph that uses Data.Map

class Graph g where
  nodeSet :: (Ord n) => g -> S.Set n
data MapGraph e n = MapGraph {mGraph :: M.Map n [(e,n)]} deriving Show

instance (Ord e,Ord n) => Graph (MapGraph e n) where
  nodeSet mapGraph = S.fromList $ M.keys $ mGraph mapGraph

The nodeSet function works correctly if I have it as a standalone function outside of the instance declaration

testNodeSet mapGraph = S.fromList $ M.keys $ mGraph mapGraph

*Main> testNodeSet $ mapGraphFromEdgeList $ parseEdgeList connectionList
fromList ["B","C","D","E","F","G","H","I","J","K","L","S"]

But I get a compile error from it inside of the instance

Main.hs:59:22: error:
    • Couldn't match type ‘n1’ with ‘n’
      ‘n1’ is a rigid type variable bound by
        the type signature for:
          nodeSet :: forall n1. Ord n1 => MapGraph e n -> S.Set n1
        at Main.hs:59:3-9
      ‘n’ is a rigid type variable bound by
        the instance declaration
        at Main.hs:58:10-46
      Expected type: S.Set n1
        Actual type: S.Set n
    • In the expression: S.fromList $ M.keys $ mGraph mapGraph
      In an equation for ‘nodeSet’:
          nodeSet mapGraph = S.fromList $ M.keys $ mGraph mapGraph
      In the instance declaration for ‘Graph (MapGraph e n)’
    • Relevant bindings include
        mapGraph :: MapGraph e n (bound at Main.hs:59:11)
        nodeSet :: MapGraph e n -> S.Set n1 (bound at Main.hs:59:3)
   |
59 |   nodeSet mapGraph = S.fromList $ M.keys $ mGraph mapGraph
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I have no idea what the error message is trying to tell me except that I have something wrong with type signatures. The type of tetsNodeSet looks correct to me

testNodeSet :: Ord a => MapGraph e a -> S.Set a

What do I need to do to fix my issue? I did wonder if I need to add (Ord e,Ord n) => to my declaration of the data type MapGraph but I couldn't work out how to put it there without getting a compile error


Solution

  • The class is making a very strong promise, which is hard to keep later on:

    class Graph g where
      nodeSet :: (Ord n) => g -> S.Set n
    

    This is promising that, from any Graph type g, we can build a set of n, for any ordered type n.

    That is, if we have instance Graph G where ... then from G we must be able to construct a nodeSet of type S.Set Bool, another of type S.Set String, and so on for S.Set Int, S.Set [Bool], etc.

    Likely, this is not what you want. You don't want to promise "from a graph you can get a set of any type", but something like "from a graph you can get a set of nodes of a fixed type which depends on the graph type".

    This dependency can be expressed in Haskell in many ways, e.g. using type families

    class Graph g where
        type Node g
        nodeSet :: g -> S.Set (Node g)   -- no need for Ord right now
    

    Here we declare a type family Node g which depends on g. Then we can write

    instance (Ord e,Ord n) => Graph (MapGraph e n) where
       type Node (MapGraph e n) = n
       nodeSet mapGraph = S.fromList $ M.keys $ mGraph mapGraph
    

    to define the type of nodes specific to the instance at hand.

    You will need to enable a bunch of Haskell extensions to make this work, but hopefully the idea is clear.


    Here's how to read the compiler's error:

    • Couldn't match type ‘n1’ with ‘n’
    

    A type n1 should have been equal to n, but it was not. Here's where these two type come from:

      ‘n1’ is a rigid type variable bound by
        the type signature for:
          nodeSet :: forall n1. Ord n1 => MapGraph e n -> S.Set n1
    

    n1 is the type which is mentioned in the class (the one called n in your code)

      ‘n’ is a rigid type variable bound by
        the instance declaration
    

    n is the type mentioned din the instance (which bares no relation to n1).

      Expected type: S.Set n1
        Actual type: S.Set n
    

    The class promised S.Set n1, but your code returned a S.Set n. To make these two types equal, we would need n1 and n equal, but there is no guarantee that they are such.