在Haskell中,我如何获取m-ary谓词和n-ary谓词并构造一个(m + n)-ary谓词?

时间:2012-10-03 20:29:22

标签: haskell typeclass polyvariadic

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

conjunction (>2) even
对于大于2的偶数和

将返回一个谓词,该谓词的计算结果为true

conjunction (>=) (<=)

将返回=

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

:t conjunction (>) not

将返回Ord a =&gt; a - &gt; a - &gt;布尔 - &gt;布尔。可以这样做吗?如果是这样,怎么样?

2 个答案:

答案 0 :(得分:6)

此解决方案需要TypeFamilies

{-# LANGUAGE TypeFamilies #-}

我们的想法是为n-ary谓词定义一个类Pred

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

问题在于重新调整谓词的参数,所以这就是本课程的目标。关联类型Arg应该通过将最终Bool替换为k来访问n-ary谓词的参数,因此如果我们有类型

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 ->开头,我们继续在Arg上递归调用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)也很简单。

答案 1 :(得分: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

希望它不言自明,但如果没有,请随意提问。

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

相关问题