Typeclass for functions with different number of arguments

In my simple Haskell DSL, I have the following functions to call other functions:

callF :: forall a. (Typeable a) => (V a) -> (V a) callF fp@ (V (FunP name)) = pack $ FunAppl (prettyV fp) [] callF1 :: forall a b. (Typeable a, Typeable b) => (V (V a -> V b)) -> V a -> (V b) callF1 fp@ (V (FunP name)) arg = pack $ FunAppl (prettyV fp) [prettyV arg] callF2 :: forall ab c. (Typeable a, Typeable b, Typeable c) => (V (V a -> V b -> V c)) -> V a -> V b -> (V c) callF2 fp@ (V (FunP name)) arg1 arg2 = pack $ FunAppl (prettyV fp) [prettyV arg1, prettyV arg2] 

I would like to generalize this to any number of arguments using type classes.

This is what I tried, but it only works for 0 or 1 arguments, and it does not provide call functions with the correct number of arguments.

 class Callable ab | a -> b where call :: V a -> [String] -> b instance Callable a (V b) where call fp@ (V (FunP name)) x = pack $ FunAppl (prettyV fp) x instance (Typeable c, Typeable d) => Callable (V a -> V b) (V c -> V d) where call fun@ (V (FunP name)) list arg = call fun (list ++ [prettyV arg]) 
+6
source share
1 answer

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.

+9
source

Source: https://habr.com/ru/post/945170/


All Articles