First, understand that PrintfArg a => [a] not a heterogeneous list. That is, although Int and String are instances of PrintfArg , [ 1 :: Int, "foo" ] not a valid construct.
So, if you defined the function :: PrintfArg a => String -> [a] -> String , then all arguments will have the same type.
To get around this, you can use existential quantification.
{-# LANGUAGE ExistentialQuantification #-} import Text.Printf data PrintfArgT = forall a. PrintfArg a => P a printfa :: PrintfType t => String -> [ PrintfArgT ] -> t printfa format = printfa' format . reverse where printfa' :: PrintfType t => String -> [ PrintfArgT ] -> t printfa' format [] = printf format printfa' format (P a:as) = printfa' format as a main = do printfa "hello world\n" [] printfa "%s %s\n" [ P "two", P "strings"] printfa "%d %d %d\n" (map P $ [1 :: Int, 2, 3]) printfa "%d %s\n" [ P (1 :: Int), P "is the loneliest number" ]
The reason your first solution fails is because you passed res for the step as an argument.
If you have foo :: Constraint a => a -> t , you guarantee that foo will work on all instances of Constraint . Although there is an instance of PrintfType that can take an argument, not all instances can. So your compiler error.
In contrast, when you have foo :: Constraint a => t -> a , you guarantee that foo will return any desired instance of Constraint . Again, the caller gets the choice of that instance. That's why my code works - when printfa' recurses, it requires that the recursive call return the value from the instance (PrintfArg a, PrintfType t) => a -> t .
For your second attempt, the compiler complains because foldr requires the accumulated value to be of the same type between iterations. The GHC notes that the accumulated value must be a function type (PrintfArg a, PrintfType t) => a -> t , because you use it in an iterated function. But you return the application value that it can determine is of type t . This means that t is equal to a -> t , which GHC does not like, because it does not allow infinite types. Therefore he complains.
If you want to use a fold, you can simply mask the battery type with Rank2Types or RankNTypes to keep the type constant between iterations.
{-