有几个问题(例如这个和这个)询问是否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
法律,但我确实检查了Identity
和Maybe
。
现在我们有
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) -> r
与a
是同构的。
这些单极变压器是完全不同的。一个例子是"括号"可以在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
其他类似的例子可以从延续/共密单子的变体中找到,尽管我还没有看到一个通用的方案。
状态单子对应于状态单子转换器和Codensity
和ReaderT
的组合:
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
中。
这个例子显示了一个带有两个变压器的单子,没有以任何方式使用高密度结构。