haskellfunctional-dependenciestype-level-computation

Make Haskell type system understand that fundeps carries over a composite (tuple-like) type


I have the following code, which I use to convert a HList-of-touples (or more like, tree-of -touples) into the same shape, but instead of a tuple, just keeping the second element.

The problem is, this needs UndecidableInstances (see the error message in comment). Is it possible to make the type system aware that indeed, a :. c determines b :. d so the error (and the need for UndecidableInstances) to go away?

class TupSnd a b | a -> b where                                                                                                
  tupSnd :: a -> b                    
                                                                                                                               
instance TupSnd PGTup (Only Action) where                                                                                      
    tupSnd a = Only (snd a)                                 
                                                               
-- This needs UndecidableInstances allegedly, but probably there's some type-level trick to make it work?
--                                                                                                                             
-- Error:                    
--                                                    
--   Reason: lhs type ‘a :. c’ does not determine rhs type ‘b :. d’
--                                                                                                                             
-- Which sounds like it shouldn't be the case logically.                                                                       
instance (TupSnd a b, TupSnd c d) => TupSnd (a :. c) (b :. d) where       
    tupSnd (a :. c) = tupSnd a :. tupSnd c

For context, :. is the glorified tuple constructor helper from postgresql-simple, and I also have

type PGTup = (Identifier, Action)
type PGTup2 = PGTup :. PGTup   
type PGTup2_2 = PGTup2 :. PGTup2                         
type PGTup3 = PGTup :. PGTup2
type PGTup4 = PGTup :. PGTup3   


Solution

  • You can enable UndecidableInstances, it's a harmless extension.

    Or, alternatively, you could switch to type families. The following code compiles (with no other extension beyond TypeFamilies). In the code below, I slightly changed your types so to avoid importing a bunch of modules. You should be able to easily adapt it back to your case.

    {-# LANGUAGE TypeFamilies #-}
    
    class TupSnd a where
       type T a
       tupSnd :: a -> T a
    
    instance TupSnd Bool where
       type T Bool = String
       tupSnd a = show a
    
    instance (TupSnd a, TupSnd c) => TupSnd (a, c) where
       type T (a, c) = (T a, T c)
       tupSnd (a, c) = (tupSnd a, tupSnd c)
    

    Personally, I find type families simpler to use than functional dependencies, so I prefer to use them whenever possible.