我们将需要TypeFamilies
这个解决方案。
{-# LANGUAGE TypeFamilies #-}
这个想法是Pred
为 n 元谓词定义一个类:
class Pred a where
type Arg a k :: *
split :: a -> (Bool -> r) -> Arg a r
问题在于重新调整谓词的参数,所以这就是该类的目标。关联类型应该通过将 final替换Arg
为 来访问 n 元谓词的参数,所以如果我们有一个类型Bool
k
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
ABool
没有参数,所以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 ->
,然后我们继续Arg
递归调用 on r
。对于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
)也很简单。