在Haskell中monad的monad转换器是唯一的吗?



有几个问题(例如这个和这个)询问是否Haskell中的每个单子(IO除外)都有相应的单子转换器。现在我想问一个补充的问题。是否每个单子都有一个变压器(或没有,如在IO的情况下),还是可以有多个变压器?

一个反例是两个单子转换器,当应用于同一单子时,它们产生的单子行为相同,但应用于其他单子时,它们产生的单子行为不同。如果答案是,一个单子可以有多个转换器,我想有一个Haskell的例子,这是尽可能简单。这些不一定是真正有用的转换器(尽管那会很有趣)。

链接问题中的一些答案似乎表明一个单子可以有多个转换器。然而,除了范畴的基本定义之外,我对范畴论所知不多,所以我不确定它们是否是这个问题的答案。

这里有一个关于唯一性反例的想法。我们知道,一般来说,单子不组成…但我们知道,如果有一个合适的swap操作,你可以组合它们[1]!让我们为单子创建一个类,让单子可以自己交换。

-- | laws (from [1]):
-- swap . fmap (fmap f) = fmap (fmap f) . swap
-- swap . pure = fmap pure
-- swap . fmap pure = pure
-- fmap join . swap . fmap (join . fmap swap) = join . fmap swap . fmap join . swap
class Monad m => Swap m where
swap :: m (m a) -> m (m a)
instance Swap Identity where swap = id
instance Swap Maybe where
swap Nothing = Just Nothing
swap (Just Nothing) = Nothing
swap (Just (Just x)) = Just (Just x)

然后我们可以构建一个monad转换器,将monad与自身组合在一起,如下所示:

newtype Twice m a = Twice (m (m a))

希望pure(<$>)的作用是显而易见的。而不是定义(>>=),我将定义join,因为我认为这是更明显的;(>>=)可以由此衍生。

instance Swap m => Monad (Twice m) where
join = id
. Twice                        -- rewrap newtype
. fmap join . join . fmap swap -- from [1]
. runTwice . fmap runTwice     -- unwrap newtype
instance MonadTrans Twice where lift = Twice . pure

我没有检查lift是否遵守所有Swap实例的MonadTrans法律,但我确实检查了IdentityMaybe

现在我们有

IdentityT Identity ~= Identity ~= Twice Identity
IdentityT Maybe    ~= Maybe   !~= Twice Maybe

这表明IdentityT不是产生Identity的唯一单抗变压器。

[1] Mark p . Jones and Luc Duponcheel作曲

单位单子至少有两个单子变压器:单位单子变压器和共密单子变压器。

newtype IdentityT m a = IdentityT (m a)
newtype Codensity m a = Codensity (forall r. (a -> m r) -> m r)

事实上,考虑到Codensity Identity,forall r. (a -> r) -> ra是同构的。

这些单极变压器是完全不同的。一个例子是"括号"可以在Codensity中定义为一元动作:

bracket :: Monad m => m () -> m () -> Codensity m ()
bracket before after = Codensity (k -> before *> k () *> after)

而将该签名转置到IdentityT则没有多大意义

bracket :: Monad m => m () -> m () -> IdentityT m ()  -- cannot implement the same functionality

其他类似的例子可以从延续/共密单子的变体中找到,尽管我还没有看到一个通用的方案。

状态单子对应于状态单子转换器和CodensityReaderT的组合:

newtype StateT s m a = StateT (s -> m (s, a))
newtype CStateT s m a = CStateT (Codensity (ReaderT s m) a)

列表单子至少对应三个单子转换器,不包括错误的单子转换器:

newtype ListT m a = ListT (m (Maybe (a, ListT m a)))  -- list-t
newtype LogicT m a = LogicT (forall r. (a -> m r -> m r) -> m r -> m r)  -- logict
newtype MContT m a = MContT (forall r. Monoid r => (a -> m r) -> m r))

前两个可以分别在包的list-t中找到(在管道中也以等价的形式),和logict。

还有一个单子的例子,它有两个不相等的转换器:"selection"单子。

type Sel r a = (a -> r) -> a

单子并不为人所知,但有论文提到它。这是一个关于一些论文的包裹:

https://hackage.haskell.org/package/transformers-0.6.0.4/docs/Control-Monad-Trans-Select.html

这个包实现了一个转换器:

type SelT r m a = (a -> m r) -> m a

但是存在第二个变压器:

type Sel2T r m a = (m a -> r ) -> m a

证明这个变压器的定律比较困难,但我已经做到了。

第二个变压器的优点是它在m中是协变的,因此可以定义hoist函数:

hoist :: (m a -> n a) -> Sel2T r m a -> Sel2T r n a

第二个变压器"功能齐全",有所有升降机和"吊装机"。第一个变压器功能不全;例如,不能定义blift :: Sel r a -> SelT r m a。换句话说,您不能将Sel的一元计算嵌入到SelT中,就像您不能对Continuation monad和Codensity monad这样做一样。

但是有了Sel2T变压器,所有升降机都存在,并且可以将Sel的计算嵌入到Sel2T中。

这个例子显示了一个带有两个变压器的单子,没有以任何方式使用高密度结构。

最新更新