今天我玩了使用类型类来归纳地构造任何arity谓词的函数,将任何类型的任何组合作为输入,返回相同类型的其他谓词但应用了一些基本操作。例如
conjunction (>2) even
对于大于2的偶数和,将返回一个谓词,该谓词的计算结果为true
conjunction (>=) (<=)
将返回=
一切都很好,让这部分工作,但它提出了一个问题,如果我想将两个谓词的连接定义为一个谓词,为每个连接谓词的每个输入获取一个输入,该怎么办?例如:
:t conjunction (>) not
将返回Ord a =&gt; a - &gt; a - &gt;布尔 - &gt;布尔。可以这样做吗?如果是这样,怎么样?
答案 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
希望它不言自明,但如果没有,请随意提问。
此外,你可以将这两个类合并为一个,当然,但我觉得这两个类使这个想法更清晰。