简体   繁体   English

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

[英]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 ,我们现在可以使用三个参数, xa类型。 We can feed x to f and then call split on the result. 我们可以将xf ,然后在结果上调用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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM