[英]In Haskell how can I take an m-ary predicate and an n-ary predicate and construct a (m+n)-ary predicate?
Today I played with using type classes to inductively construct functions of a predicate of any arity taking as inputs any combination of any types, that returned other predicates of the same type but with some basic operation applied. 今天我玩了使用类型类来归纳地构造任何arity谓词的函数,将任何类型的任意组合作为输入,返回相同类型的其他谓词但应用了一些基本操作。 For example
例如
conjunction (>2) even
would return a predicate that evaluates to true for even numbers larger than two and 会返回一个谓词,对于大于2的偶数,它的计算结果为真
conjunction (>=) (<=)
would return = 会返回=
All well and good, got that part working, but it raised the question, what if I wanted to define conjunction of two predicates as being a predicate that takes one input for each input of each conjoined predicate? 一切都很好,让这部分工作,但它提出了一个问题,如果我想将两个谓词的连接定义为一个谓词,为每个连接谓词的每个输入获取一个输入,该怎么办? For example:
例如:
:t conjunction (>) not
would return Ord a => a -> a -> Bool -> Bool. 将返回Ord a => a - > a - > Bool - > Bool。 Can this be done?
可以这样做吗? If so, how?
如果是这样,怎么样?
We will need TypeFamilies
for this solution. 我们将需要
TypeFamilies
用于此解决方案。
{-# LANGUAGE TypeFamilies #-}
The idea is to define a class Pred
for n-ary predicates: 我们的想法是为n-ary谓词定义一个类
Pred
:
class Pred a where
type Arg a k :: *
split :: a -> (Bool -> r) -> Arg a r
The problem is all about re-shuffling arguments to the predicates, so this is what the class aims to do. 问题在于重新调整谓词的参数,所以这就是本课程的目标。 The associated type
Arg
is supposed to give access to the arguments of an n-ary predicate by replacing the final Bool
with k
, so if we have a type 相关类型
Arg
应该通过用k
替换最终的Bool
来访问n-ary谓词的参数,所以如果我们有一个类型
X = arg1 -> arg2 -> ... -> argn -> Bool
then 然后
Arg X k = arg1 -> arg2 -> ... -> argn -> k
This will allow us to build the right result type of conjunction
where all arguments of the two predicates are to be collected. 这将允许我们构建正确的结果类型的
conjunction
,其中要收集两个谓词的所有参数。
The function split
takes a predicate of type a
and a continuation of type Bool -> r
and will produce something of type Arg ar
. 函数
split
接受类型a
的谓词和Bool -> r
类型的延续,并将产生类型为Arg ar
东西。 The idea of split
is that if we know what to do with the Bool
we obtain from the predicate in the end, then we can do other things ( r
) in between. split
的想法是,如果我们知道如何处理我们最终从谓词中获得的Bool
,那么我们可以在其间做其他事情( r
)。
Not surprisingly, we'll need two instances, one for Bool
and one for functions for which the target is already a predicate: 毫不奇怪,我们需要两个实例,一个用于
Bool
,一个用于目标已经是谓词的函数:
instance Pred Bool where
type Arg Bool k = k
split b k = k b
A Bool
has no arguments, so Arg Bool k
simply returns k
. Bool
没有参数,所以Arg Bool k
只返回k
。 Also, for split
, we have the Bool
already, so we can immediately apply the continuation. 另外,对于
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
If we have a predicate of type a -> r
, then Arg (a -> r) k
must start with a ->
, and we continue by calling Arg
recursively on r
. 如果我们有一个类型
a -> r
的谓词,那么Arg (a -> r) k
必须以a ->
开头,我们继续在r
递归调用Arg
。 For split
, we can now take three arguments, the x
being of type a
. 对于
split
,我们现在可以使用三个参数, x
是a
类型。 We can feed x
to f
and then call split
on the result. 我们可以将
x
给f
,然后在结果上调用split
。
Once we have defined the Pred
class, it is easy to define conjunction
: 一旦我们定义了
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))
The function takes two predicates and returns something of type Arg a (Arg b Bool)
. 该函数接受两个谓词并返回类型为
Arg a (Arg b Bool)
。 Let's look at the example: 我们来看看这个例子:
> :t conjunction (>) not
conjunction (>) not
:: Ord a => Arg (a -> a -> Bool) (Arg (Bool -> Bool) Bool)
GHCi doesn't expand this type, but we can. GHCi没有扩展这种类型,但我们可以。 The type is equivalent to
类型相当于
Ord a => a -> a -> Bool -> Bool
which is exactly what we want. 这正是我们想要的。 We can test a number of examples, too:
我们也可以测试一些例子:
> conjunction (>) not 4 2 False
True
> conjunction (>) not 4 2 True
False
> conjunction (>) not 2 2 False
False
Note that using the Pred
class, it is trivial to write other functions (like disjunction
), too. 请注意,使用
Pred
类,编写其他函数(如disjunction
函数)也很简单。
{-# 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
Hope it's self-explanatory, but if not, feel free to ask questions. 希望它不言自明,但如果没有,请随意提问。
Also, you could merge the two classes into one, of course, but I feel that the two classes make the idea more clear. 此外,您可以将这两个类合并为一个,当然,但我觉得这两个类使这个想法更清晰。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.