【发布时间】:2017-09-07 03:01:36
【问题描述】:
我很确定我可以证明,原则上,Predicate 类型构造函数是 ContraMonad,是 Monad 的泛化,其中函子是逆变的,但我不确定它会如何实施。
首先,让我们定义一些术语:
class Contravariant f where
contramap :: (b -> a) -> f a -> f b
class ContraMonad m where
return :: a -> m a
join :: m(m a) -> m a
contrabind :: m a -> (m b -> a) -> m b
data Predicate a = Pred {getPred :: a -> Bool}
instance Contravariant Predicate where
contramap f x = Pred ((getPred x).f)
这表明谓词是一个从 a 取值并从中生成命题的函数,是从 Hask 到 Hask^{op} 的逆变函子。谓词的一个例子是:
isEven :: Integer -> Bool
isEven n = if (mod n 2 == 0)
then True
else False
函数isEven 是数学意义上的谓词,而Pred isEven 是此处实现的意义上的Predicate。
将Predicate 实现为ContraMonad 比较棘手。
return只有一种自然选择。
return :: a -> Predicate a
return x = Pred(const True)
您可能会考虑“返回”给出一个谓词的可能性,该谓词 x 为真,并且没有其他类型为 a 的元素为真。问题是只有当 a 是 Eq 类型类的成员时才能实现。
考虑加入比contrabind 更容易。显然,我们想要
contrabind x f = join.contramap f x
这样 contrabind 在考虑 f 作为逆变函子的同时尽可能地类似于 bind。函数f 将Pred b 转换为a,因此contramap f 将Pred a 转换为Pred(Pred b)。
那么,join 应该怎么做呢?它必须将 a 类型的谓词的谓词转换为 a 类型的谓词。 a 类型谓词的谓词对 a 类型的谓词进行判断。例如,考虑 a = Integer。 Pred( Pred Integer) 的一个例子是:
Pred("The predicate f is true for all even numbers.")
我用引号代替了实际的实现。如果 f 对所有偶数都为真,则此陈述为真。例如,Pred isEven 的计算结果为 True。
考虑到集合 A 上的谓词对应于 A 的子集这一事实,Pred(Pred A) 是一个函数的包装器,它采用 A 的所有子集并将它们判断为“真”或“假”。我们希望join 给我们一个谓词,这与给A 的一个子集相同。这个子集应该尽可能无损。事实上,它应该关心每一个Pred X 的真值,w.r.t. Pred(Pred X) 判断。在我看来,自然的解决方案似乎是所有被判断为“真”的子集的交集,这与将所有真谓词“与”在一起
predAnd :: (a -> Bool) -> (a -> Bool) -> (a -> Bool)
predAnd p q = \x -> ((getPred p) $ x) && ((getPred q) $ x)
作为一个例子,让我们回到“谓词 f 对所有偶数都为真”。在此判断下,每个被评估为True 的谓词对于所有偶数都必须为真,因此每个可能的子集都包含偶数。所有这些集合的交集将只是偶数集合,因此join 将返回谓词Pred isEven。
我的问题是,“加入”实际上是如何实现的?可以实施吗?我可以看到对于像“整数”这样的无限类型产生的不可判定集的潜在问题,但它甚至可以为像“Char”这样的有限类型实现,即使幂集也具有有限的基数?
【问题讨论】:
-
我注意到
\f x -> contrabind (return x) f :: (m b -> a) -> a -> m b。不知何故,我不喜欢我们如何改变这些函数的方向:每个 contramonad 都必须支持这一点,这似乎很难实现。此外,任何b的contrabind (return ()) (const ()) : m b看起来都很奇怪。 -
函数被翻转,因为 m 是一个逆变函子。因此,来自
m b -> a的函数变成来自m a -> m (m b))的函数。只要它是逆变的而不是协变的,每个反单子都可以实现这一点。你可以写contrabind x f = join.contramap (f x)。contramap f函数几乎总是通过使用 f 进行预合成来工作,而不是使用 f 进行正常的后合成。这就是大多数逆变函子的工作方式。ContraMonad应该与Monad完全相同,只是它是逆变的。因此,return和join不应更改。