具有多个参数的函数的常规技术——比如——是printf
使用递归类型类。因为printf
,这是通过一个名为 的类来完成的PrintfType
。重要的见解是递归实例:
(PrintfArg a, PrintfType r) => PrintfType (a -> r)
这基本上是说,如果您可以返回 a PrintfType
,那么您的函数也是一个实例。“基本情况”是一种类型,如String
. 所以,如果你想printf
用一个参数调用,它会触发两个实例:PrintfType String
和PrintfType (a -> r)
where r
is String
。如果你想要两个参数,它是:String
, (a -> r)
where r
isString
和(a -> r)
where r
is the previous (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
您还必须启用一些扩展FlexibleInstances
:FucntionalDepenedencies
和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] :. ()'
)的重要部分确实指出了核心问题(不匹配的参数列表),这应该很容易理解。
*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" :. ())
请注意,这对于您的特定任务可能有点过于复杂——我不相信这是解决问题的最佳方法。但这是一个很好的练习,可以使用一些更高级的类型类特性来强制执行更复杂的类型级不变量。