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)
那么我们可以构建一个单子变换器,将一个单子与自身组合起来,如下所示:
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
的唯一单子变换器。swap
的单子。夏立尧的回答没有这个限制,似乎是一个“真实”的例子。然而,它比你的回答难得多。我希望我能接受你们两个的回答。 - QuantumWizjoin
但仍然以它为基础,(>>=)
会是什么。我得到了 x >>= f = Twice $ fmap join $ join $ fmap swap $ fmap (fmap f) $ runTwice $ x
。这正确吗还是我犯了错误? - QuantumWizfmap (fmap (runTwice . f))
而不是fmap (fmap f)
)。除此之外,我认为那看起来是正确的。 - Daniel Wagner身份单子至少有两个单子变换器:身份单子变换器和密度单子变换器。
newtype IdentityT m a = IdentityT (m a)
newtype Codensity m a = Codensity (forall r. (a -> m r) -> m r)
事实上,考虑到Codensity Identity,对于所有的r,(a -> r) -> r与a是同构的。
这些单子变换器非常不同。一个例子是,“bracket”可以在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
类似的例子也可以从延续/余续单子的变体中找到,但我还没有看到一个通用的方案。
状态单子对应于状态单子转换器以及余续单子和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))
Codensity
既是一个Monad Transformer,同时又是一个Monad,它不需要被应用到另一个Monad上就可以成为Monad
的一个实例? - QuantumWizinstance Monad (Codensity f) where
(或在较新的 GHC 版本中为 instance Monad (Codensity (f :: k -> TYPE rep)) where
)。似乎没有限制 f 必须是一个 monad。例如,MaybeT
的 monad 实例声明是 instance (Monad m) => Monad (MaybeT m) where
,因此要求 m 是一个 monad。 - QuantumWiz还有一个例子是具有两个不等价变换器的单子:选择单子。
type Sel r a = (a -> r) -> a
Monad并不是很出名,但有一些论文提到它。这里有一个引用了一些论文的软件包:
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
中。
这个例子展示了一个带有两个变压器的monad,而没有以任何方式使用Codensity构造。
a
的值,给定一个由类型a-> r
的函数表示的"选择标准"。在最简单的非平凡情况下,取r = Boolean
,所以类型为a -> r
的函数只是一个断言,说明类型为a
的给定值是"好"还是"坏"。类型为(a -> Boolean) -> a
的选择函数将以某种方式返回满足这种断言的类型为a
的值。 - undefined
OuterMaybeT
,它与一般的MaybeT
不同,但确实给出了OuterMaybeT Identity ≌ Maybe
。但仅适用于应用,我认为它不能用作单子变换器,而且我实际上从未检查过它是否符合法律。 - leftaroundaboutOuterMaybe f
只是Compose Maybe f
,对吧?所以据我所见它应该是合法的。 - amalloyT
,使得T Identity ~= Maybe
和T [] ~= []
,对吧?所以……不,它们并不是唯一的。这有点像退化的例子,但我真的想不出一个清晰的属性来形式化我的直觉,即我认为哪些是退化的,哪些不是。也许有一些参数性的类型级别类似物之类的东西……? - Daniel WagnerT Identity
和T []
分别提供两个独立的instance
声明吗?当然,如果是这样的话,每个声明可以按照自己的方式定义。但是,这感觉更像是两个单独的转换器而不是一个转换器(作为 MaybeT 的替代方案)。我认为对于“非退化”情况来说,转换器只需要有一个形式为instance (Monad m) => Monad (T m) where
的声明就足够了。不确定这是否要求过高。到目前为止,我只是粗略地浏览了答案,但我想在仔细阅读它们后会更明智。 - QuantumWiz