此解决方案需要TypeFamilies。
{-# LANGUAGE TypeFamilies #-}
这个想法是为 n 元谓词定义一个类Pred:
class Pred a where
type Arg a k :: *
split :: a -> (Bool -> r) -> Arg a r
问题在于对谓词的参数重新洗牌,所以这就是该类的目标。关联类型 Arg 应该通过将最后的 Bool 替换为 k 来访问 n 元谓词的参数,所以如果我们有一个类型
X = arg1 -> arg2 -> ... -> argn -> Bool
然后
Arg X k = arg1 -> arg2 -> ... -> argn -> k
这将允许我们构建conjunction 的正确结果类型,其中两个谓词的所有参数都将被收集。
函数split 接受a 类型的谓词和Bool -> r 类型的延续,并将产生Arg a r 类型的东西。 split 的想法是,如果我们知道最后从谓词中获得的Bool 可以做什么,那么我们可以在两者之间做其他事情(r)。
毫不奇怪,我们需要两个实例,一个用于Bool,另一个用于目标已经是谓词的函数:
instance Pred Bool where
type Arg Bool k = k
split b k = k b
Bool 没有参数,所以Arg Bool k 只返回k。另外,对于split,我们已经有了Bool,所以我们可以立即申请延续。
instance Pred r => Pred (a -> r) where
type Arg (a -> r) k = a -> Arg r k
split f k x = split (f x) k
如果我们有一个a -> r 类型的谓词,那么Arg (a -> r) k 必须以a -> 开头,然后我们继续在r 上递归调用Arg。对于split,我们现在可以采用三个参数,x 的类型为a。我们可以将x 提供给f,然后在结果上调用split。
一旦我们定义了Pred类,就很容易定义conjunction:
conjunction :: (Pred a, Pred b) => a -> b -> Arg a (Arg b Bool)
conjunction x y = split x (\ xb -> split y (\ yb -> xb && yb))
该函数接受两个谓词并返回Arg a (Arg b Bool) 类型的内容。我们来看例子:
> :t conjunction (>) not
conjunction (>) not
:: Ord a => Arg (a -> a -> Bool) (Arg (Bool -> Bool) Bool)
GHCi 没有扩展这种类型,但我们可以。类型等价于
Ord a => a -> a -> Bool -> Bool
这正是我们想要的。我们也可以测试一些例子:
> conjunction (>) not 4 2 False
True
> conjunction (>) not 4 2 True
False
> conjunction (>) not 2 2 False
False
请注意,使用Pred 类,编写其他函数(如disjunction)也很简单。