7

今天,我尝试使用类型类来归纳构造任意数量的谓词的函数,将任意类型的任意组合作为输入,返回相同类型但应用了一些基本操作的其他谓词。例如

conjunction (>2) even

将返回一个谓词,该谓词对于大于 2 的偶数计算为真,并且

conjunction (>=) (<=)

会返回=

一切都很好,让这部分工作,但它提出了一个问题,如果我想将两个谓词的连接定义为一个谓词,每个连接谓词的每个输入都需要一个输入,该怎么办?例如:

:t conjunction (>) not

将返回 Ord a => a -> a -> Bool -> Bool。这可以做到吗?如果是这样,怎么做?

4

2 回答 2

6

我们将需要TypeFamilies这个解决方案。

{-# LANGUAGE TypeFamilies #-}

这个想法是Pred为 n 元谓词定义一个类:

class Pred a where
  type Arg a k :: *
  split :: a -> (Bool -> r) -> Arg a r

问题在于重新调整谓词的参数,所以这就是该类的目标。关联类型应该通过将 final替换Arg为 来访问 n 元谓词的参数,所以如果我们有一个类型Boolk

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类型的存在ax我们可以喂给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)也很简单。

于 2012-10-03T21:14:42.530 回答
4
{-# LANGUAGE TypeFamilies #-}

class RightConjunct b where
  rconj :: Bool -> b -> b

instance RightConjunct Bool where
  rconj = (&&)

instance RightConjunct b => RightConjunct (c -> b) where
  rconj b f = \x -> b `rconj` f x

class LeftConjunct a where
  type Ret a b
  lconj :: RightConjunct b => a -> b -> Ret a b

instance LeftConjunct Bool where
  type Ret Bool b = b
  lconj = rconj

instance LeftConjunct a => LeftConjunct (c -> a) where
  type Ret (c -> a) b = c -> Ret a b
  lconj f y = \x -> f x `lconj` y

conjunction :: (LeftConjunct a, RightConjunct b) => a -> b -> Ret a b
conjunction = lconj

希望它是不言自明的,但如果没有,请随时提出问题。

另外,当然,您可以将两个类合并为一个,但我觉得这两个类使这个想法更加清晰。

于 2012-10-03T20:51:14.303 回答