ChatGPT解决这个技术问题 Extra ChatGPT

应用程序组成,单子不组成

应用程序组成,单子不组成。

上面的说法是什么意思?什么时候一个比另一个更可取?

你从哪里得到这个声明?查看一些上下文可能会有所帮助。
@FUZxxl:我从许多不同的人那里反复听到过,最近来自 twitter 上的 debasishg。
@stephen tetley:请注意,许多这样的 Applicative 实际上是一个完整的 family Monad,即每个可能的结构“形状”都有一个。 ZipList 不是 Monad,但固定长度的 ZipList 是。 Reader 是一种方便的特殊情况(还是一般情况?),其中“结构”的大小固定为环境类型的基数。
@CAMcCann 如果您以相当于同构的 Reader monad 的方式修复形状,所有这些 zippy 应用程序(无论它们是截断还是填充)都限制为 monad。一旦你修复了一个容器的形状,它就可以有效地从位置编码一个函数,就像一个备忘录树。彼得汉考克称这样的函子为“Naperian”,因为它们遵守对数定律。
@stephen tetley:其他例子包括常量单子应用程序(它是单子的组合,但不是单子)和单位延迟应用程序(最好不要承认加入)。

p
pigworker

如果我们比较类型

(<*>) :: Applicative a => a (s -> t) -> a s -> a t
(>>=) :: Monad m =>       m s -> (s -> m t) -> m t

我们得到了区分这两个概念的线索。 (>>=) 类型中的 (s -> m t) 表明 s 中的值可以确定 m t 中的计算行为。单子允许值层和计算层之间的干扰。 (<*>) 运算符不允许这样的干扰:函数和参数计算不依赖于值。这真是咬人。相比

miffy :: Monad m => m Bool -> m x -> m x -> m x
miffy mb mt mf = do
  b <- mb
  if b then mt else mf

它使用某种效果的结果来决定两种计算(例如发射导弹和签署停战协议),而

iffy :: Applicative a => a Bool -> a x -> a x -> a x
iffy ab at af = pure cond <*> ab <*> at <*> af where
  cond b t f = if b then t else f

它使用 ab 的值在 两个计算 ataf 的值之间进行选择,同时执行了这两个计算,可能会产生悲剧性的效果。

monadic 版本本质上依赖于 (>>=) 的额外功能来从值中选择计算,这可能很重要。然而,支持这种权力使得单子难以组合。如果我们尝试构建“双重绑定”

(>>>>==) :: (Monad m, Monad n) => m (n s) -> (s -> m (n t)) -> m (n t)
mns >>>>== f = mns >>-{-m-} \ ns -> let nmnt = ns >>= (return . f) in ???

我们已经走到了这一步,但现在我们的图层都混乱了。我们有一个 n (m (n t)),所以我们需要去掉外部的 n。正如 Alexandre C 所说,如果我们有合适的

swap :: n (m t) -> m (n t)

n 向内排列并将 join 排列到另一个 n

较弱的“双重应用”更容易定义

(<<**>>) :: (Applicative a, Applicative b) => a (b (s -> t)) -> a (b s) -> a (b t)
abf <<**>> abs = pure (<*>) <*> abf <*> abs

因为层与层之间没有干扰。

相应地,当您真正需要 Monad 的额外功能以及何时可以摆脱 Applicative 支持的刚性计算结构时,这很好。

顺便提一下,尽管编写 monad 很困难,但它可能比你需要的要多。 m (n v) 类型表示使用 m-effects 计算,然后使用 n-effects 计算 v-value,其中 m-effects 在 n-effects 开始之前完成(因此需要swap)。如果您只想将 m-效果与 n-效果交错,那么组合可能太难了!


对于不确定的示例,您声明它“使用 ab 的值在 at 和 af 的两个计算值之间进行选择,同时执行了这两个计算,可能会产生悲剧性的效果。” Haskell 的懒惰特性不能保护你免受这种情况的影响吗?如果我有 list = (\btf -> if b then t else f) : [] 然后执行语句: list <*> pure True <*> pure "hello" <*> pure (error "bad")。 ...我得到“你好”,错误永远不会发生。这段代码不像 monad 那样安全或受控,但该帖子似乎暗示应用程序会导致严格的评估。不过总体来说很棒!谢谢!
您仍然可以获得两者的效果,但是 pure (error "bad") 没有任何效果。另一方面,如果你尝试 iffy (pure True) (pure "hello") (error "bad"),你会得到一个 miffy 避免的错误。此外,如果你尝试类似 iffy (pure True) (pure 0) [1,2],你会得到 [0,0] 而不是 [0]。应用程序对它们有一种严格性,因为它们构建了固定的计算序列,但是正如您所观察到的,这些计算产生的值仍然是惰性组合的。
对任何 monad mn,您总是可以编写一个 monad 转换器 mt,并使用 mt n tn (m t) 中操作,这是真的吗?所以你总是可以组合单子,只是更复杂,使用变压器?
这种转换器经常存在,但据我所知,没有生成它们的规范方法。关于如何解决来自不同 monad 的交错效应,通常有一个真正的选择,典型的例子是异常和状态。异常是否应该回滚状态更改?两种选择都有自己的位置。话虽如此,有一个“自由单子”表示“任意交错”。 data Free f x = Ret x | Do (f (Free f x)),然后是 data (:+:) f g x = Inl (f x) | Tnr (g x),然后考虑 Free (m :+: n)。这会延迟选择如何运行交错。
@Arek'Fu 你忽略了“纯”。它是纯粹的(错误“坏”),没有任何影响,或者是任何纯粹的(任何东西)。
C
Conal

应用程序组成,单子不组成。

单子组合,但结果可能不是单子。相反,两个应用程序的组合必然是一个应用程序。我怀疑原始陈述的意图是“应用性构成,而单子性不构成”。改写为“Applicative 在作文下是封闭的,而 Monad 不是。”


此外,任何两个应用程序都以完全机械的方式组合,而由两个单子组合形成的单子是特定于该组合的。
而且单子以其他方式组成,两个单子的乘积是一个单子,只有联积才需要某种分配律。
包含@Apocalisp 的评论,这是最好和最简洁的答案。
J
Joseph Sible-Reinstate Monica

如果您有应用程序 A1A2,则类型 data A3 a = A3 (A1 (A2 a)) 也是应用程序(您可以以通用方式编写这样的实例)。

另一方面,如果您有 monad M1M2,则类型 data M3 a = M3 (M1 (M2 a)) 不一定是 monad(组合的 >>=join 没有合理的通用实现)。

一个例子可以是类型 [Int -> a](这里我们用 (->) Int 组成一个类型构造函数 [],它们都是单子)。您可以轻松编写

app :: [Int -> (a -> b)] -> [Int -> a] -> [Int -> b]
app f x = (<*>) <$> f <*> x

这可以推广到任何应用程序:

app :: (Applicative f, Applicative f1) => f (f1 (a -> b)) -> f (f1 a) -> f (f1 b)

但是没有合理的定义

join :: [Int -> [Int -> a]] -> [Int -> a]

如果您不相信这一点,请考虑以下表达式:

join [\x -> replicate x (const ())]

返回列表的长度必须在提供整数之前一成不变,但它的正确长度取决于提供的整数。因此,此类型不存在正确的 join 函数。


...所以当函数可以执行时避免使用 monad?
@andrew,如果您的意思是函子,那么是的,函子更简单,应该在足够的时候使用。请注意,并非总是如此。例如,没有 MonadIO 将很难编程。 :)
L
Landei

不幸的是,我们真正的目标,单子的组合,是相当困难的。 .. 事实上,我们实际上可以证明,在某种意义上,仅使用两个 monad 的操作是无法构造具有上述类型的连接函数的(参见附录中的证明大纲)。因此,我们可能希望形成一个组合的唯一方法是,如果有一些额外的结构连接这两个组件。

组成单子,http://web.cecs.pdx.edu/~mpj/pubs/RR-1004.pdf


Tl;博士为不耐烦的读者:如果(f?)您可以提供自然转换swap : N M a -> M N a,您可以编写单子
@Alexandre C.:我怀疑只是“如果”。并非所有的单子变换器都由直接函子组合来描述。例如,ContT r m a 既不是 m (Cont r a) 也不是 Cont r (m a),而 StateT s m a 大致是 Reader s (m (Writer s a))
@CA McCann:我似乎无法从 (M monad, N monad, MN monad, NM monad) 到(存在交换:MN -> NM natural)。所以现在让我们坚持“如果”(也许答案在论文中,我必须承认我很快就查到了)
@Alexandre C.:仅仅指定组合是单子可能还不够——你还需要一些方法来将这两个部分与整体联系起来。 swap 的存在意味着该组合让两者以某种方式“合作”。另外,请注意 sequence 是某些 monad 的“交换”的特例。 flip 实际上也是如此。
要编写 swap :: N (M x) -> M (N x),在我看来,您可以使用 returns(最好是 fmapped)在前面插入一个 M,在后面插入一个 N,从 N (M x) -> M (N (M (N x))) 开始,然后使用join 的合成得到你的 M (N x)
u
user278559

分配律解 l : MN -> NM 就足够了

以保证 NM 的一元性。要看到这一点,您需要一个单元和一个 mult。我将专注于 mult(单位是 unit_N unitM)

NMNM - l -> NNMM - mult_N mult_M -> NM

这并不能保证 MN 是一个单子。

然而,当你有分配律解决方案时,关键的观察就会发挥作用

l1 : ML -> LM
l2 : NL -> LN
l3 : NM -> MN

因此,LM、LN 和 MN 是单子。问题在于 LMN 是否是一个单子(通过

(MN)L -> L(MN) 或按 N(LM) -> (LM)N

我们有足够的结构来制作这些地图。然而,作为 Eugenia Cheng observes,我们需要一个六边形条件(相当于 Yang-Baxter 方程的表示)来保证任一结构的一元性。事实上,在六边形条件下,两个不同的单子是重合的。


这可能是一个很好的答案,但它让我头晕目眩。
那是因为,使用术语 Applicative 和 haskell 标签,这是一个关于 haskell 的问题,但答案是不同的符号。
w
winitzki

任何两个应用函子都可以组合并产生另一个应用函子。但这不适用于单子。两个单子的组合并不总是单子。例如,StateList 单子的组合(以任何顺序)不是单子。

此外,一般来说,无论是通过组合还是通过任何其他方法,都不能组合两个单子。没有已知的算法或程序可以将任意两个单子 MN 组合成一个更大、合法的单子 T,这样您就可以通过单子态射注入 M ~> TN ~> T 并满足合理的非退化定律(例如,为了保证 T 不只是一个丢弃来自 MN 的所有效果的单位类型)。

可以为特定的 MN 定义一个合适的 T,例如 M = MaybeN = State s 等等。但是不知道如何定义可以在单子 MN 中以参数方式工作的 T。函子组合和更复杂的结构都不能充分发挥作用。

组合单子 MN 的一种方法是首先定义联积 C a = Either (M a) (N a)。这个 C 将是一个函子,但通常不是一个 monad。然后在仿函数 C 上构造一个自由单子 (Free C)。结果是一个能够表示 MN 组合效果的 monad。然而,它是一个更大的单子,也可以代表其他效果;它远大于 MN 的效果组合。此外,为了提取任何结果,需要“运行”或“解释”免费的 monad(并且只有在“运行”之后才能保证 monad 法则)。会有运行时损失和内存大小损失,因为自由单子在“运行”之前可能会在内存中构建非常大的结构。如果这些缺点不显着,那么免费的单子就是要走的路。

组合 monad 的另一种方法是获取一个 monad 的转换器并将其应用于另一个 monad。但是没有算法方法来定义一个 monad(例如,Haskell 中的类型和代码)并产生相应转换器的类型和代码。

至少有 4 种不同类别的 monad,它们的转换器以完全不同但规则的方式构造(内部组合、外部组合、基于附加的 monad、product monad)。其他一些 monad 不属于这些“常规”类中的任何一个,并且以某种方式将转换器定义为“临时”。

分配律只存在于组合的单子中。认为可以定义某个函数 M (N a) -> N (M a) 的任何两个单子 MN 将组成是一种误导。除了使用这种类型签名定义函数外,还需要证明某些定律成立。在许多情况下,这些法律并不成立。

甚至有些单子有两个不等价的转换器;一个以“常规”方式定义,一个以“临时”方式定义。一个简单的例子是身份单子Id a = a;它有常规的转换器 IdT m = m(“组合”)和不规则的“临时”转换器:IdT2 m a = forall r. (a -> m r) -> m rm 上的共密度单子)。

一个更复杂的例子是“selector monad”:Sel q a = (a -> q) -> a。这里 q 是一个固定类型,而 a 是 monad Sel q 的主要类型参数。这个 monad 有两个转换器:SelT1 m a = (m a -> q) -> m a (composed-inside) 和 SelT2 m a = (a -> m q) -> m a (ad hoc)。

完整的细节在“函数式编程的科学”一书的第 14 章中制定。 https://github.com/winitzki/sofphttps://leanpub.com/sofp/


t
tillmo

这是一些通过分配律工作的代码制作单子组合。请注意,从任何单子到单子 MaybeEitherWriter[] 都有分配律。另一方面,您不会在 ReaderState 中找到这样的(一般)分配律。对于这些,您将需要 monad 转换器。

 {-# LANGUAGE FlexibleInstances #-}
 
 module ComposeMonads where
 import Control.Monad
 import Control.Monad.Writer.Lazy
 
 newtype Compose m1 m2 a = Compose { run :: m1 (m2 a) }
 
 instance (Functor f1, Functor f2) => Functor (Compose f1 f2) where
   fmap f = Compose . fmap (fmap f) . run
 
 class (Monad m1, Monad m2) => DistributiveLaw m1 m2 where
   dist :: m2 (m1 a) -> m1 (m2 a)
 
 instance (Monad m1,Monad m2, DistributiveLaw m1 m2)
           => Applicative (Compose m1 m2) where
     pure = return
     (<*>) = ap
 
 instance (Monad m1, Monad m2, DistributiveLaw m1 m2)
           => Monad (Compose m1 m2) where
   return = Compose . return . return
   Compose m1m2a >>= g =
     Compose $ do m2a <- m1m2a -- in monad m1
                  m2m2b <- dist $ do a <- m2a  -- in monad m2
                                     let Compose m1m2b = g a
                                     return m1m2b
                                  -- do ... ::  m2 (m1 (m2 b))
                           -- dist ... :: m1 (m2 (m2 b))          
                  return $ join m2m2b -- in monad m2
 
 instance Monad m => DistributiveLaw m Maybe where
   dist Nothing = return Nothing
   dist (Just m) = fmap Just m
 
 instance Monad m => DistributiveLaw m (Either s) where
   dist (Left s) = return $ Left s
   dist (Right m) = fmap Right m
 
 instance Monad m => DistributiveLaw m [] where
   dist = sequence
 
 instance (Monad m, Monoid w) => DistributiveLaw m (Writer w) where
   dist m = let (m1,w) = runWriter m
            in do a <- m1
                  return $ writer (a,w)
 
 liftOuter :: (Monad m1, Monad m2, DistributiveLaw m1 m2) =>
                    m1 a -> Compose m1 m2 a
 liftOuter = Compose . fmap return
 
 liftInner :: (Monad m1, Monad m2, DistributiveLaw m1 m2) =>
                    m2 a -> Compose m1 m2 a
 liftInner = Compose . return