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

agr*_*fix 6 dsl haskell function 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]
Run Code Online (Sandbox Code Playgroud)

我想对使用类型类的任意数量的参数进行概括.

这是我尝试过的,但它只适用于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])
Run Code Online (Sandbox Code Playgroud)

Tik*_*vis 9

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

(PrintfArg a, PrintfType r) => PrintfType (a -> r)
Run Code Online (Sandbox Code Playgroud)

这基本上说如果你可以返回一个PrintfType,你的函数也是一个实例.那么"基本情况"就是这样的类型String.所以,如果你想打电话printf一个参数,它触发两个实例:PrintfType StringPrintfType (a -> r)地方rString.如果你想要两个参数,那就是:String,(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
Run Code Online (Sandbox Code Playgroud)

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

"foo" :. "bar"          :: String :. String
"foo" :. "bar" :. "baz" :: String :. String :. String
Run Code Online (Sandbox Code Playgroud)

现在,您只需编写一个带有递归实例的类型类,该实例遍历参数的类型级列表和函数本身.这是一个非常粗略的独立草图,我的意思; 你必须自己采纳它来解决你的问题.

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
Run Code Online (Sandbox Code Playgroud)

您还必须启用了一些扩展:FlexibleInstances,FucntionalDepenedenciesUndecidableInstances.

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

*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"
Run Code Online (Sandbox Code Playgroud)

如果传入错误数量的参数,则会出现类型错误.不可否认,这不是世界上最漂亮的错误信息!也就是说,error(Couldn't match type `()' with `[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" :. ())
Run Code Online (Sandbox Code Playgroud)

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