Function 具有不同参数数的函数的Typeclass

Function 具有不同参数数的函数的Typeclass,function,haskell,dsl,typeclass,Function,Haskell,Dsl,Typeclass,在我的简单Haskell DSL中,我有以下函数调用其他函数: 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) ca

在我的简单Haskell DSL中,我有以下函数调用其他函数:

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 a b 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]
我想将其推广到使用类型类的任意数量的参数

这是我尝试过的,但它只适用于0或1个参数,并且不能强制使用正确数量的参数调用函数

class Callable a b | 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])

具有多个参数的函数(如
printf
)的常规技术是使用递归类型类。对于
printf
,这是通过一个名为的类完成的。重要的细节是递归实例:

(PrintfArg a, PrintfType r) => PrintfType (a -> r)
这基本上是说,如果您可以返回一个
PrintfType
,那么您的函数也是一个实例。“基本情况”是一种类似于
String
的类型。因此,如果您想用一个参数调用
printf
,它会触发两个实例:
PrintfType String
PrintfType(a->r)
其中
r
String
。如果需要两个参数,它是:
String
(a->r)
其中
r
String
(a->r)
其中
r
是前面的
(a->r)

然而,您的问题实际上要复杂一些。您希望有一个实例来处理两个不同的任务。您希望实例应用于不同类型的函数(例如
V(va->vb)
V(va->vb->vc)
等等),并确保提供正确数量的参数

执行此操作的第一步是停止使用
[String]
传递参数。
[String]
类型会丢失有关它有多少个值的信息,因此无法检查参数的数量是否合适。相反,您应该为参数列表使用一个类型来反映它有多少个参数

这种类型可能看起来像这样:

data a :. b = a :. b
"foo" :. "bar"          :: String :. String
"foo" :. "bar" :. "baz" :: String :. String :. String
*Main> call (V "foo") ()
V "foo"
*Main> call (V (\ x -> "foo " ++ x)) ("bar" :. ())
V "foo bar"
*Main> call (V (\ x y -> "foo " ++ x ++ y)) ("bar" :. " baz" :. ())
V "foo bar baz"
它只是一个用于组合其他两种类型的类型,可以这样使用:

data a :. b = a :. b
"foo" :. "bar"          :: String :. String
"foo" :. "bar" :. "baz" :: String :. String :. String
*Main> call (V "foo") ()
V "foo"
*Main> call (V (\ x -> "foo " ++ x)) ("bar" :. ())
V "foo bar"
*Main> call (V (\ x y -> "foo " ++ x ++ y)) ("bar" :. " baz" :. ())
V "foo bar baz"
现在您只需要编写一个带有递归实例的typeclass,该实例遍历类型级别的参数列表和函数本身。这是我的意思的一个非常粗略的单独草图;你必须自己采用它来解决你的特殊问题

infixr 8 :.
data a :. b = a :. b

class Callable f a b | f -> a b 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 (f a)) rest

instance Callable String () (V String) where
  call (V f) () = V f
您还必须启用一些扩展:
FlexibleInstances
functionaldependencies
不可判定实例

然后您可以这样使用它:

data a :. b = a :. b
"foo" :. "bar"          :: String :. String
"foo" :. "bar" :. "baz" :: String :. String :. String
*Main> call (V "foo") ()
V "foo"
*Main> call (V (\ x -> "foo " ++ x)) ("bar" :. ())
V "foo bar"
*Main> call (V (\ x y -> "foo " ++ x ++ y)) ("bar" :. " baz" :. ())
V "foo bar baz"
如果传入的参数数量错误,则会出现类型错误。诚然,这不是世界上最漂亮的错误信息!也就是说,错误的重要部分(
无法将类型“()”与“[Char]:.()”
)匹配)确实指出了核心问题(不匹配的参数列表),应该很容易理解

*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 -> a b'
        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" :. ())
*Main>调用(V(\x->“foo”++x))(“bar:”baz:()
:101:1:
无法将类型“()”与“[Char]:”匹配。()'
使用函数依赖项进行组合时
可调用字符串()(V字符串),
由依赖项'f->a b'引起
在/home/tikhon/Documents/so/call.hs:16:14的实例声明中
可调用的[Char]([Char]:.())(V[Char]),
因在101:1-4使用“call”而产生
在表达式中:
调用(V(\x->“foo”++x))(“bar:”.“baz:()
在“it”的方程式中:
它=调用(V(\x->“foo”++x))(“bar:”baz:()

请注意,对于您的特定任务来说,这可能有点过于复杂——我不认为这是解决问题的最佳方案。但是,在使用一些更高级的类型类特性强制执行更复杂的类型级别不变量时,这是一个非常好的练习。

请记住,
va->vb->vc
实际上是
va->(vb->vc)
。你可以通过归纳法来做到这一点。基本情况:
va
可调用。归纳案例:假设
va
是可调用的,那么
vb->va
是可调用的。归纳案例的实例头将具有一般形式
实例可调用k=>可调用(m->k),其中