申请人撰写,monad没有

时间:2011-08-12 13:35:42

标签: haskell functional-programming monads monad-transformers applicative

  

申请人撰写,monad没有。

上述陈述是什么意思?什么时候比其他人更好?

7 个答案:

答案 0 :(得分:107)

如果我们比较类型

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

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

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难以构成。如果我们试图建立'双重绑定'

(>>>>==) :: (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 s的额外功能时,以及当你可以摆脱Applicative支持的刚性计算结构时,这是很好的。

顺便说一句,请注意,尽管编写monad很困难,但它可能比你需要的更多。类型m (n v)表示使用m进行计算 - 效果,然后使用n计算 - 效果为v - 值,其中m - 效果在n - 效果开始(因此需要swap)。如果您只想将m - 效果与n - 效果交错,那么合成可能太多了!

答案 1 :(得分:70)

  

申请人撰写,monad没有。

Monads 撰写,但结果可能不是monad。 相反,两种应用的组合物必然是一种应用。 我怀疑原始陈述的意图是“应用性构成,而monadness不构成。”改写,“Applicative在撰写时关闭,Monad不是。”

答案 2 :(得分:38)

如果您有应用A1A2,则类型data A3 a = A3 (A1 (A2 a))也适用(您可以通用方式编写此类实例)。

另一方面,如果你有单子M1M2,那么data M3 a = M3 (M1 (M2 a))类型不一定是monad(>>=没有合理的通用实现或者join用于作文)。

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

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函数。

答案 3 :(得分:17)

  

不幸的是,我们真正的目标,monad的组成,更多   难。 ..其实我们   实际上可以证明,从某种意义上说,没有办法   使用上面的类型构造一个连接函数   两个monad的操作(参见附录中的概述)   证明)。因此,我们可能希望形成一个唯一的方式   组成是否有一些额外的结构链接   两个组成部分。

撰写monad,http://web.cecs.pdx.edu/~mpj/pubs/RR-1004.pdf

答案 4 :(得分:7)

分配法解决方案     l:MN - &gt; NM就足够了

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

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

保证MN是monad。

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

l1 : ML -> LM
l2 : NL -> LN
l3 : NM -> MN
因此,LM,LN和MN是单子。问题是LMN是否是monad(通过

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

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

答案 5 :(得分:1)

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

此外,无论是通过组合还是通过任何其他方法,通常都不能组合两个 monad。没有已知的算法或程序可以将任意两个 monad M, N 组合成一个更大的合法 monad T,以便您可以注入 M ~> TN ~> T由 monad 态射并满足合理的非简并定律(例如,保证 T 不仅仅是一种丢弃来自 MN 的所有效果的单位类型)。

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

组合单子 MN 的一种方法是首先定义联积 C a = Either (M a) (N a)。这个 C 将是一个函子,但通常不是一个 monad。然后在函子 Free C 上构造一个自由 monad (C)。结果是一个能够表示 MN 组合效果的 monad。然而,它是一个更大的单子,也可以代表其他效果;它比 MN 的效果组合要大得多。此外,自由 monad 需要“运行”或“解释”以提取任何结果(并且仅在“运行”之后才能保证 monad 定律)。将会有运行时惩罚以及内存大小惩罚,因为空闲 monad 可能会在“运行”之前在内存中构建非常大的结构。如果这些缺点不显着,那么免费 monad 是您的最佳选择。

另一种结合 monad 的方法是将一个 monad 的转换器应用到另一个 monad 上。但是没有算法方法可以定义 monad(例如 Haskell 中的类型和代码)并生成相应转换器的类型和代码。

至少有 4 种不同类别的 monad,它们的 Transformer 以完全不同但有规律的方式构建(composed-inside、composite-outside、adjunction-based monad、product monad)。其他一些 monad 不属于这些“常规”类中的任何一个,并且以某种方式定义了“临时”转换器。

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

甚至有些 monad 有两个不等价的 Transformer;一种以“常规”方式定义,另一种是“临时”定义。一个简单的例子是身份 monad 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/

答案 6 :(得分:0)

这里是一些通过分配律工作使 monad 组合的代码。请注意,从任何 monad 到 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