具有不同参数数量的函数的类型类

时间:2013-05-16 13:44:13

标签: 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)
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])

1 个答案:

答案 0 :(得分:9)

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

(PrintfArg a, PrintfType r) => PrintfType (a -> r)

这基本上说如果你可以返回PrintfType,你的功能也是一个实例。然后,“基本案例”类似于String。因此,如果您想使用一个参数调用printf,则会触发两个实例:PrintfType StringPrintfType (a -> r)其中r为{{1} }。如果你想要两个参数,那就是:StringString其中(a -> r)rString其中(a -> r) }是之前的r

但是,您的问题实际上有点复杂。您希望拥有一个处理两个不同任务的实例。您希望您的实例应用于不同类型的函数(例如(a -> r)V (V a -> V b)等),并确保显示正确数量的参数。

执行此操作的第一步是停止使用V (V a -> V b -> V c)传递参数。 [String]类型会丢失有关它有多少值的信息,因此您无法检查是否存在适当数量的参数。相反,您应该使用参数列表的类型来反映它有多少参数。

此类型可能如下所示:

[String]

它只是一种用于组合其他两种类型的类型,可以像这样使用:

data a :. b = a :. b

现在你只需要编写一个带有递归实例的类型类,该实例遍历参数的类型级列表和函数本身。这是一个非常粗略的独立草图,我的意思;你必须自己将它用于你的特定问题。

"foo" :. "bar"          :: String :. String
"foo" :. "bar" :. "baz" :: String :. String :. String

您还必须启用一些扩展程序: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 FlexibleInstancesFucntionalDepenedencies

然后您可以像这样使用它:

UndecidableInstances

如果传入错误数量的参数,则会出现类型错误。不可否认,这不是世界上最漂亮的错误信息!也就是说,错误的重要部分(*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" 确实指出了核心问题(不匹配的参数列表),这应该很容易遵循。

Couldn't match type `()' with `[Char] :. ()'

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