Search code examples
haskellrefactoringtype-inferencetypeclass

Reference inferred type in multi parameter type classes


I have a data type with 3 phantom types attached so i can define functions classes for each combination. This method removes much duplicate code:

newtype Dd x y z = ToDd Cudd.DdNode deriving (Eq,Show)

--Class for all instances where the first subtype (x) is specified
class DdT a b c where
    neg :: Dd a b c -> Dd a b c

--Class for all instances that are completely specified
class DdTFS a b c where
    bot :: Dd a b c 
    top :: Dd a b c 

with this i can define functions for multiple cases

--bot and top get no arguments, thus the instance types are fully specified
instance DdTFS B F1 S1 where
  bot = ToDd (Cudd.readLogicZero mgr)
  top = ToDd (Cudd.readOne mgr)

instance DdTFS Z F1 S1 where
  bot = ToDd $ Cudd.zddReadZero
  top = ToDd $ Cudd.zddReadOne
-- ..etc for every combination of types

--x is set to B, y and z are inferred
instance DdT B a b where
  neg (ToDd b) = ToDd $ Cudd.cuddNot b

--x is set to Z, y and z are inferred
instance DdT Z a b where
  neg (ToDd z) = ToDd $ Cudd.ifthenelse (ToDd z) (bot :: Dd Z a b) (top :: Dd Z a b)

In the last line lies my problem, i had hoped ghci would infer (for a and b) the data types from the given argument, z. Instead i get "No instance for (DdTFS Z a1 b1) arising from a use of ‘top’" and "No instance for (DdTFS Z a1 b1) arising from a use of ‘bot’" when compiling. Is there an easy fix for what i am trying to achieve? or am i approaching the problem wrong?

Edit: I tried

neg z = ToDd $ Cudd.ifthenelse z ((bot mgr)`asTypeOf` z) ((bot mgr)`asTypeOf` z)

but this tells me that "No instance for (DdTFS Z a b) arising from a use of ‘bot’"

Edit 2: replaced "z" in the last line with (ToDd z)


Solution

  • If you want the compiler to infer some types of a MPTC for you, you need to declare a suitable functional dependency in the class.

    {-# LANGUAGE FunctionalDependencies      #-}
    
    class DdT a b c | a->b, a->c where
    

    But that doesn't really seem to be what you want. In particular, the instances you tried wouldn't work any better this way.

    Instead, I think the DdT should only have one parameter, and instead the method neg should quantify over b and c. Also, to use methods of the DdTFS class there, you need to constrain accordingly:

    class DdT a where
      neg :: DdTFS a b c => Dd a b c -> Dd a b c
    

    Then it is a bit awkward to not have the b and c variables explicitly in scope, but as you already discovered asTypeOf helps in this case:

    instance DdT Z where
      neg z = ToDd $ Cudd.ifthenelse z botdd topdd
       where ToDD botdd = bot `asTypeOf` z
             ToDD topdd = top `asTypeOf` z