A common method for functions with multiple arguments, such as printf , is to use a recursive type. For printf this is done using the PrintfType class. An important insight is a recursive instance:
(PrintfArg a, PrintfType r) => PrintfType (a -> r)
Basically, this suggests that if you can return PrintfType , your function is also an instance. A "base register" is a type of type String . So, if you want to call printf with one argument, it starts two instances: PrintfType String and PrintfType (a -> r) , where r is String . If you need two arguments, these are: String , (a -> r) where r is String and (a -> r) , where r is the previous (a -> r) .
However, your problem is actually a bit more complicated. You want to have an instance that handles two different tasks. You want your instance to apply to functions of different types (for example, V (V a -> V b) , V (V a -> V b -> V c) , etc.), and also to present the correct one number of arguments.
The first step for this is to stop using [String] to pass arguments. The [String] loses information about how many values ββit matters, so you cannot check for the corresponding number of arguments. Instead, you should use a type for argument lists that reflects the number of arguments it has.
This type might look something like this:
data a :. b = a :. b
it's just a type to combine two other types, which can be used as follows:
"foo" :. "bar" :: String :. String "foo" :. "bar" :. "baz" :: String :. String :. String
Now you just need to write a type class with a recursive instance that traverses both the list of type arguments and the function itself. Here is a very crude separate outline of what I mean; You will have to take it to your specific problem yourself.
infixr 8 :. data a :. b = a :. b class Callable fab | f -> ab where call :: V f -> a -> b instance Callable rf ra (V rb) => Callable (String -> rf) (String :. ra) (V rb) where call (V f) (a :. rest) = call (V (fa)) rest instance Callable String () (V String) where call (V f) () = V f
You also need to enable several extensions: FlexibleInstances , FucntionalDepenedencies and UndecidableInstances .
Then you can use it as follows:
*Main> call (V "foo") () V "foo" *Main> call (V (\ x -> "foo " ++ x)) ("bar" :. ()) V "foo bar" *Main> call (V (\ xy -> "foo " ++ x ++ y)) ("bar" :. " baz" :. ()) V "foo bar baz"
If you pass the wrong number of arguments, you will get a type error. Admittedly, this is not the nicest error message in the world! However, the important part of the error ( Couldn't match type `()' with `[Char] :. ()' ) Points to the main problem (lists of arguments that do not match), which should be simple enough to follow.
*Main> call (V (\ x -> "foo " ++ x)) ("bar" :. "baz" :. ()) <interactive>:101:1: Couldn't match type `()' with `[Char] :. ()' When using functional dependencies to combine Callable String () (V String), arising from the dependency `f -> ab' in the instance declaration at /home/tikhon/Documents/so/call.hs:16:14 Callable [Char] ([Char] :. ()) (V [Char]), arising from a use of `call' at <interactive>:101:1-4 In the expression: call (V (\ x -> "foo " ++ x)) ("bar" :. "baz" :. ()) In an equation for `it': it = call (V (\ x -> "foo " ++ x)) ("bar" :. "baz" :. ())
Please note that this can be a little more complicated for your specific task - I'm not sure if this is the best solution to the problem. But this is a very good exercise for applying more complex type-invariants using more advanced class functions.