单子变换器是所有标准单子(Reader、Writer、State、Cont、List等)的已知概念,但每个单子变换器的工作方式略有不同。没有通用的方法或公式可以根据具有单子实例的类型构造一个单子变换器。因此,不能保证根据某些任意业务要求设计的单子数据类型将具有单子变换器。是否有这样一个明确的例子?
相关工作
另一个问题解释说两个单子的函子组合不一定是单子。请参见此问题。这些示例并未回答当前问题,它们只是说明了构造单子变换器没有通用方法的问题。这些示例表明,给定两个单子M和N,我们有时会发现M(N a)是单子,有时N(M a)是单子,有时两者都不是单子。但这既不显示如何为M或N构造单子变换器,也不显示它是否存在。
另一个问题的答案认为IO
单子不能有单子变换器,因为如果它有一个IOT
,我们可以将IOT
应用于List
,然后将空列表(lift []
)提升到结果单子中,必须撤消IO单子“先前”执行的副作用。这个论点基于这样一个想法,即IO
单子“实际执行”副作用,假定这些副作用不能被撤消。然而,IO
单子不是一个显式的类型构造函数。
讨论
在每个明确给出单子类型的示例中,总能找到某种单子变换器 - 有时需要一定的创造力。例如,存在于Haskell库中的ListT
变换器最近相对较近地发现以微妙的方式不正确,但通过改变ListT
的定义最终解决了这个问题。
没有转换器的单子的标准例子是像IO
这样的单子,实际上并没有由显式类型构造函数定义 - IO
是由库在低级别上以某种方式定义的不透明“魔法”类型。很明显,不能用纯函数给出单子实例来定义IO
作为显式类型构造函数。 IO
示例表明,如果我们允许单子实例包含具有不纯副作用的隐藏低级代码,则单子转换器可能不存在。因此,让我们将注意力限制在使用纯函数实现的单子上。
似乎没有算法可以从单子源代码自动派生单子转换器。我们甚至知道这总是可能的吗?
为了更清楚地说明我所说的单子的“显式示例”,假设我声称
type Q u v a = ((u -> (a, Maybe a)) -> v) -> u -> (a, Maybe a)
我可以为类型参数a提供一个合法的Monad实例,并为Q u v的Monad实例生产纯函数return和join的源代码。那么我们是否知道Q u v有一个Monad变换器QT u v,使得QT u v Id等价于Q u v,并且Monad变换器的规律成立?我们是否知道如何显式构建QT?我不知道。
为了解决这个问题,我们需要:
- 展示一种算法,能够从任意给定的类型构造器和一个给定的monad实例的实现中找到一个单子变换器;例如,给定代码
type F a = r -> Either (a, a) (a, a, Maybe a)
和这个类型的monad实例的实现,找到单子变换器的代码;为了简便起见,让我们将自己限制在由任何组合的->
、元组和Either
构成的类型构造器上;或者 - 展示一个反例:一个显式的monad类型构造器,由显式的代码定义给出,例如
type F a = r -> Either (a, a, a) (a, a, Maybe a)
或其他任何类型,使得这是一个合法的Monad
,具有由纯函数给出的Monad
实例,但我们可以证明,F
没有单子变换器。
IO
本身并不神奇,仅仅是它所包装的状态。newtype IO a = IO (状态 -> (状态, a))
。 - chepnerforkIO
。当然,你可以认为 magic 的魔法也处理线程,但我们正在开始探讨更深入的问题。 - luquiconst x y == x
(在单子世界中是const <$> x <*> y == x
,这是不正确的)。也许这是一个开始的地方。 - luquiListT
done right”来自于2007年。最初(有点无效的)ListT
出现在2002年的GHC 5中。因此,我不确定甚至能称之为“相对较近”。 - dfeuerT
是一个Monad变换器并且存在一个Monad同构将T Identity
和F
联系起来(也就是说,一个标准的同构映射,它遵循绑定和返回操作),那么可以假设T
是F
的一个Monad变换器。 - Daniel Wagner