到目前为止,我遇到的每个monad(可以表示为数据类型)都有相应的monad变换器,或者可以有一个。有没有这样的monad不能拥有一个?或者所有monad都有相应的变压器吗?
对应于monad t
的转换器m
,我的意思是t Identity
与m
同构。当然,它满足monad变换器定律,t n
是任何monad n
的monad。
我希望看到每个monad都有一个证明(理想情况下是建设性的证明),或者某个monad的例子(没有证明)。我对更多面向Haskell的答案以及(类别)理论答案感兴趣。
作为后续问题,是否有一个monad m
有两个不同的变换器t1
和t2
?也就是说,t1 Identity
与t2 Identity
和m
同构,但有一个monad n
,t1 n
与t2 n
不同构
(IO
和ST
有一个特殊的语义,所以我不会在这里考虑它们,让我们完全忽视它们。让我们只关注& #34; pure"可以使用数据类型构造的monad。)
答案 0 :(得分:19)
我和@Rhymoid在一起,我相信所有Monads都有两个(!!)变形金刚。我的构造有点不同,而且不太完整。我希望能够将这个草图变成一个证明,但我认为我要么缺少技能/直觉和/或它可能参与其中。
由于Kleisli,每个monad(m
)都可以分解为两个仿函数F_k
和G_k
,以便F_k
与G_k
同伴并且m
与G_k * F_k
同构(此处*
是仿函数合成)。此外,由于附加,F_k * G_k
形成了一个comonad。
我声称t_mk
定义为t_mk n = G_k * n * F_k
是monad变换器。显然,t_mk Id = G_k * Id * F_k = G_k * F_k = m
。为这个仿函数定义return
并不困难,因为F_k
是一个“尖头”仿函数,并且定义join
应该是可能的,因为来自comonad extract
的{{1}}可以用于将F_k * G_k
类型的值减少为(t_mk n * t_mk n) a = (G_k * n * F_k * G_k * n * F_k) a
类型的值,然后通过G_k * n * n * F_k
的{{1}}进一步减少。
我们必须要小心,因为join
和n
不是Hask上的endofunctors。因此,它们不是标准F_k
类型类的实例,也不能与G_k
直接组合,如上所示。相反,我们必须在撰写之前将Functor
“投射”到Kleisli类别中,但我相信来自n
的{{1}}提供了“投影”。
我相信您也可以使用Eilenberg-Moore monad分解执行此操作,为n
,return
和{{提供m
,m = G_em * F_em
和类似结构1}}与来自comonad tm_em n = G_em * n * F_em
的{{1}}具有相似的依赖关系。
答案 1 :(得分:3)
这是一个手写的,我不太确定的答案。
Monads可以被认为是命令式语言的接口。 return
是你如何在语言中注入纯粹的价值,而>>=
就是你将语言的各个部分拼接在一起的方式。 Monad法律确保语言的“重构”部分以您期望的方式工作。 monad提供的任何其他操作都可以被视为其“操作”。
Monad变形金刚是解决“可扩展效果”问题的一种方法。如果我们有Monad Transformer t
转换Monad m
,那么我们可以说语言 m
正在通过{{ 1}}。 t
monad是没有效果/操作的语言,因此将Identity
应用于t
只会让您获得仅使用Identity
提供的操作的语言。
因此,如果我们在“注入,拼接和其他操作”模型方面考虑Monads,那么我们可以使用Free Monad Transformer重新制定它们。甚至IO monad也可以通过这种方式变成变压器。唯一的问题是你可能想要某种方法在某个时刻从变换器堆栈上剥离该层,唯一合理的方法是在堆栈底部有t
,这样你就可以在那里执行操作。
答案 2 :(得分:1)
此反例的monad类型构造函数L
由
type L z a = Either a (z -> a)
此monad的目的是使用显式z -> a
值(pure
)修饰普通读者monad Left x
。普通读者monad的pure
值是一个常数函数pure x = _ -> x
。但是,如果给定类型z -> a
的值,则将无法确定该值是否为常数函数。对于L z a
,pure
的值显式表示为Left x
。用户现在可以在L z a
上进行模式匹配,并确定给定的单调值是纯值还是有效值。除此之外,monad L z
的功能与阅读器monad完全相同。
monad实例:
instance Monad (L z) where
return x = Left x
(Left x) >>= f = f x
(Right q) >>= f = Right(join merged) where
join :: (z -> z -> r) -> z -> r
join f x = f x x -- the standard `join` for Reader monad
merged :: z -> z -> r
merged = merge . f . q -- `f . q` is the `fmap` of the Reader monad
merge :: Either a (z -> a) -> z -> a
merge (Left x) _ = x
merge (Right p) z = p z
此单子L z
是更一般的结构的特定情况,(Monad m) => Monad (L m)
其中L m a = Either a (m a)
。此构造通过添加显式m
值(pure
)来修饰给定的monad Left x
,以便用户现在可以在L m
上进行模式匹配以确定该值是否为纯。通过其他所有方式,L m
代表与单子m
相同的计算效果。
L m
的monad实例与上面的示例几乎相同,除了需要使用monad join
的{{1}}和fmap
,并且辅助函数m
由
merge
我检查了单子 merge :: Either a (m a) -> m a
merge (Left x) = return @m x
merge (Right p) = p
是否适用于L m
的单子定律。
所以,我认为m
没有Monad转换器,对于一般L m
甚至是简单的Monad m
都没有。考虑上面定义的m = Reader
就足够了;即使是那个简单的monad似乎也没有变压器。
不存在monad转换器的(启发式)原因是该monad具有L z
内部和Reader
。 Either
monad变压器需要其基本monad组成在外部monad Either
内部,因为monad变压器使用遍历进行操作。看起来,在其数据类型中包含EitherT e m a = m (Either e a)
的任何monad都需要遍历才能使monad转换器工作,因此,转换器中必须有一个“内部”组成。但是,Either
monad转换器需要其基本monad由外部monad Reader
组成。 monad ReaderT r m a = r -> m a
是一种数据类型的组合,该数据类型需要内部组合变压器,而monad需要外部组合变压器,而第二个monad在第一个内部,因此无法协调。无论我们如何尝试定义L变压器L
,似乎我们都无法满足monad变压器的定律。
定义类型构造器LT
的一种可能性是LT
。结果是合法的monad,但是同态LT z m a = Either a (z -> m a)
不会保留m a -> LT z m a
的{{1}},因为m
被映射到return
中,而不是return x
的{{1}}(总是Right (\z -> return x)
)。
另一种可能性是L
。结果是单子,但是return
的{{1}}再次映射到Left x
而不是单子LT z m a = z -> Either a (m a)
所需的m
。
结合可用类型构造函数的另一种可能性是return
,但这不是任意monad \_ -> Right (...)
的monad。
我不确定如何严格证明Left (...)
没有monad转换器,但是没有类型构造器z -> Either a (m a)
,LT z m a = Either a (m (z -> a) )
和m
的组合似乎起作用正确。
因此,单子L
以及通常形式为Either
的单子似乎没有简单易用的单子转换器,它将是显式类型构造函数({{1}的组合},->
和m
)。
L z
此单子出现在“搜索单子” here的上下文中。 paper by Jules Hedges还提到了搜索单子,更普遍地,是“ selection”单子的形式
L m
对于给定的monad Either
和固定类型->
。上面的搜索单子是m
和type S a = (a -> Bool) -> Maybe a
的选择单子的一种特殊情况。但是,Hedges的论文(我认为是错误的)声称 type Sq n q a = (a -> n q) -> n a
是monad n
的monad转换器。
我的意见是,单子q
具有“外部组成”类型的单子变压器n a = Maybe a
。这与问题Is this property of a functor stronger than a monad?中探讨的“刚度”属性有关,即q = ()
是一个刚性单子,所有刚性单子都具有“外部组成”类型的单子变压器。
但是,除非单子Sq
是刚性的,否则(a -> q) -> a
并不是刚性的。由于并非所有monad都是刚性的(例如(a -> q) -> a
和(m a -> q) -> m a
都不是刚性的),因此monad (a -> q) -> a
不会具有“外部”类型的{{1} }。它也不会有“内置式”变压器(a -> n q) -> n a
-这不是任意monad n
的monad;以Maybe
为例。类似地,对于任意单子Cont
和(a -> n q) -> n a
,类型(m a -> n q) -> n (m a)
也不是单子。类型m((a -> n q) -> n a)
是任何m
的单子,但不接受提升m = Maybe
,因为仅在将某些值包装到任意单子{的情况下,我们无法计算值(a -> m (n q)) -> m (n a)
{1}}。
m
和n
都是合法的monad(我手动对其进行了检查),但是它们似乎都没有合法的monad转换器。
这是对monad变压器不存在的启发式论证。如果m(a -> n q) -> n a
的monad变换器的数据类型定义适用于所有monad m
,则该数据类型定义将产生刚性m a -> m (a -> n q) -> n a
的“组合外部”变换器,并且其他非刚性n a
的变压器。但是对于自然地和参数地使用m
的类型表达式(即,作为具有monad实例的不透明类型构造函数)的类型表达式是不可能的。
通常,转换后的单子本身并不自动拥有单子转换器。也就是说,一旦我们获取了一些外国monad S
并对其应用了monad变换器Sq
,我们将获得一个新的monad (a -> n q) -> n a
,并且该monad没有变换器:给定一个新外国单子n
,我们不知道如何用单子n
来转换n
。如果我们知道单子n
的变换器m
,我们可以先用t
变换t m
,然后用n
变换结果。但是,如果我们没有单子n
的转换器,那么我们就会陷入困境:没有一种结构可以仅凭t m
的知识为单子mt
创建一个转换器。适用于任意外国单子m
。
@JamesCandy的答案表明,对于任何monad (包括n
?!),可以编写一个(通用但复杂的)类型表达式来表示相应的monad转换器。即,您首先需要对monad类型进行Church-encode编码,这使该类型看起来像是连续monad,然后定义其monad转换器,就好像是连续monad。但是我认为这是不正确的-总体而言,它并没有给出生产monad变压器的方法。
采用类型mt
的教堂编码意味着写下类型
t
根据Yoneda的引理,此类型m
与t m
完全同构。到目前为止,除了引入量化类型参数t
,使类型变得更加复杂之外,我们什么都没做。
现在让我们对基本单子m
进行教堂编码:
IO
同样,由于a
与 type ca = forall r. (a -> r) -> r
完全等效,因此到目前为止,我们什么也没做。
现在,假装ca
是连续单子转换器(不是!),然后通过替换结果类型a
来编写monad转换器,就好像它是连续单子转换器一样。通过forall r
:
L
据称这是 type CL a = forall r. (L a -> r) -> r
的“教会编码的monad转换器”。但这似乎是不正确的。我们需要检查属性:
CL a
是任何外国monad L a
和任何基本monad CL a
的合法monad r
是合法的单子态射影第二个属性成立,但我相信第一个属性失败,换句话说,m r
不是任意单子 type TCL m a = forall r. (L a -> m r) -> m r
的单子。也许有些单子L
承认了这一点,但其他人则没有。我找不到TCL m
的通用monad实例,它对应于任意基本monad m
。
另一种认为L
通常不是monad的方法是注意到m a -> TCL m a
确实是任何类型构造函数TCL m
的monad。用m
表示此单子。现在,m
。如果TCL m
是单子,则意味着L
可以与任何单子TCL m
组成,并产生合法的单子forall r. (a -> m r) -> m r
。但是,非平凡的单子m
(尤其是不等同于CM
的单子)与所有单子TCL m a = CM (L a)
组成的可能性很小。在没有严格限制的情况下,单子通常不会构成。
一个不起作用的特定示例适用于阅读器monad。考虑TCL m
和CM
,其中L
和CM (L a)
是一些固定类型。现在,我们要考虑“教会编码的monad变换器” CM
。我们可以使用Yoneda引理简化该类型表达式,
Reader
(对于任何函子L
)并获取
L a = r -> a
因此,在这种情况下,这是m a = s -> a
的类型表达式。如果r
是monad转换器,则s
将是monad。但是,人们可以明确地检查此forall t. (L a -> m t) -> m t
实际上不是单子(一个人不能实现满足法律的 forall t. (x -> t) -> Q t = Q x
和Q
)。
即使这行得通(即假设我在声称 forall t. (L a -> s -> t) -> s -> t
= forall t. ((L a, s) -> t) -> s -> t
= s -> (L a, s)
= s -> (r -> a, s)
通常不是monad时犯了错误),这种构造也有一些缺点:
TCL m a
,它不是函数式的(即,不是协变的),因此我们不能做诸如将转换后的单子转换成另一个单子或合并两个单子转换器的操作,如{{3 }} TCL
的存在使该类型的推理变得相当复杂,并可能导致性能下降(请参阅“认为有害的教堂编码”文章)和堆栈溢出(因为教堂编码通常不是堆栈安全的) )P a = s -> (r -> a, s)
)的教堂编码单子转换器不会产生未修改的外国单子:P
,并且与return
不同。实际上,给定单子bind
,要弄清楚那个单子是什么很难。作为一个显示TCL m
为什么使推理变得复杂的示例,请考虑外国单子m
并尝试了解类型forall r
的实际含义。我无法简化这种类型,也无法找到关于这种类型的含义的很好的解释,即它代表什么样的“效果”(因为它是单子,所以必须代表某种“效果”)以及如何使用这样的类型。
L = Id
,T m a = forall r. (a -> m r) -> m r
,m a
,m
等。 / li>
目前尚不清楚还有多少其他monad变压器,以及在什么情况下会使用一个或另一个变压器。但是,到目前为止,我还没有看到一个合法monad具有不止一个(等效)合法monad转换器的示例。
答案 3 :(得分:0)
我的解决方案利用了Haskell术语的逻辑结构。每个人都知道具有返回类型t的Haskell中的函数可以变成具有返回类型(Monad m)=>的monadic函数。 m t。因此,如果“绑定”函数可以使其程序文本适当地“monadified”,那么结果将是monad变换器。
关键在于没有理由为什么“绑定”运算符的“monadification”应该满足法则,特别是相关性。这就是切割消除的地方。切割消除定理对内联所有let-bindings,case-analysis和applications的程序文本有影响。此外,特定术语的所有计算最终都在一个地方执行。
由于“bind”的类型参数是刚性的,因此“bind”右侧的使用由它们的返回值索引。这些术语最终返回到返回结构中使“绑定”关联的位置,因此右侧的使用必须在“monadified”“bind”中是关联的,并且得到的结构是monad。
这真的是毛茸茸的,所以这是一个例子。请考虑以下严格列表monad:
rseq x y = case x of
x:xs -> (x:xs) : y
[] -> [] : y
evalList (x:xs) = rseq x (evalList xs)
evalList [] = []
instance Monad [] where
return x = [x]
ls >>= f = concat (evalList (map f ls))
此评估顺序导致标准ListT(不是真正的monad)。但是,通过削减消除:
instance Monad [] where
return x = [x]
ls >>= f = case ls of
[] -> []
x:xs -> case f x of
y:ys -> (y:ys) ++ (xs >>= f)
[] -> [] ++ (xs >>= f)
这提供了“monadified”的确切评估顺序。
回应Petr Pudlak:
如果所讨论的monad的类型是某种函数类型(教会编码所有数据类型很方便),则通过使用转换后的monad修饰该类型的所有返回值来转换函数类型。这是monadification的类型部分。 monadification的值部分使用“return”提升纯函数,并使用“bind”将它们与monad类型的居民的使用相结合,保留原始程序文本的评估顺序。
严格列表monad是作为评估顺序的一个例子,它没有关联组合,正如ListT使用与严格列表monad相同的评估顺序这一事实。
要完成该示例,列表monad的Church编码为:
data List a = List (forall b. b -> (a -> List a -> b) -> b)
Monadified,它变成:
data ListT m a = ListT (forall b. m b -> (a -> List a -> m b) -> m b)
cons x xs = \_ f -> f x xs
nil = \x _ -> x
instance (Monad m) => Monad (ListT m) where
return x = cons x nil
ls >>= f = ls nil (\x xs -> f x >>= \g ->
g (liftM (nil ++) (xs >>= f)) (\y ys -> liftM (cons y ys ++) (xs >>= f))
为了详细说明上述内容,切割消除步骤强制使用堆栈规则消耗所有值,确保结构中的结果顺序与评估顺序相匹配 - 这可能会导致可能运行相同的操作很多次。
[技术上,你需要的是近似的切割消除:A是B的切割消除(近似),iff对于B的每个有限近似,存在A的有限近似,使得A是切割消除B。]
我希望有所帮助。