定义,法律和例证

可能重复:
什么是monad?

我正在学习使用Haskell的函数式语言进行编程,并且在学习解析器时遇到了Monads 。 我以前从来没有听说过他们,所以我做了一些额外的研究,以了解他们是什么。

我为了学习这个主题而到处看到的只是让我感到困惑。 我无法真正找到Monad是什么以及如何使用它们的简单定义。 “monad是一种使用这些值根据值和计算序列构造计算结构的方法” - eh ???

有人可以提供一个Monad在Haskell中的简单定义,与它们相关的法律并给出一个例子吗?

  • 注意:我知道如何使用do语法,因为我查看了带有副作用的I / O操作和函数。

  • 直觉

    粗略的直觉是Monad是一种特殊的容器( Functor ),为此你有两种可用的操作。 包装操作return将单个元素放入容器中。 将容器容器合并到单个容器中的操作join

    return :: Monad m => a -> m a     
    join   :: Monad m => m (m a) -> m a
    

    所以对于Monad也许你有:

    return :: a -> Maybe a     
    return x = Just x
    
    join :: Maybe (Maybe a) -> Maybe a
    join (Just (Just x) = Just x
    join (Just Nothing) = Nothing 
    join Nothing        = Nothing
    

    对于Monad []同样,这些操作被定义为:

    return :: a -> [a]     
    return x = [x]
    
    join :: [[a]] -> [a]
    join xs = concat xs
    

    Monad的标准数学定义基于这些返回和加入运算符。 然而,在Haskell中,类Monad的定义将绑定运算符用于加入。

    Monas在Haskell

    在函数式编程语言中,这些特殊的容器通常用于表示有效的计算。 Maybe a类型Maybe a代表可能成功或不成功的计算,而类型[a]是非确定性的计算。 特别是我们对具有效果的函数感兴趣,例如某些Monad m类型为a->mb 。 我们需要能够撰写它们。 这可以使用monadic组合或绑定运算符来完成。

    (>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
    (>>=) :: Monad m => m a -> (a -> m b) -> m b
    

    在Haskell中,后者是标准的。 请注意,它的类型与应用程序运算符的类型非常相似(但带有翻转的参数):

    (>>=)    :: Monad m => m a -> (a -> m b) -> m b
    flip ($) ::              a -> (a ->   b) -> b
    

    它需要一个有效函数f :: a -> mb和一个计算mx :: ma返回类型为a值,并执行应用程序mx >>= f 。 那么我们如何与Monads做到这一点? 容器( Functors )可以被映射,在这种情况下,结果是一个计算内部的计算,然后可以被展平:

    fmap f mx        :: m (m b)
    join (fmap f mx) :: m b
    

    所以我们有:

    (mx >>= f) = join (fmap f mx) :: m b
    

    要在实践中看到这个工作,请考虑一个列表(非确定性函数)的简单示例。 假设你有一个可能的结果列表mx = [1,2,3]和一个非确定性函数fx = [x-1, x*2] 。 要计算mx >>= f ,首先将mx与f映射,然后合并结果::

    fmap f mx                = [[0,2],[1,4],[2,6]]    
    join [[0,2],[1,4],[2,6]] = [0,2,1,4,2,6]
    

    由于在Haskell中,绑定操作符(>>=)join更重要,因为后者中的效率原因是由前者定义的,而不是其他方式。

    join mx = mx >>= id
    

    此外,绑定操作符(由join和fmap定义)也可用于定义映射操作。 出于这个原因Monad不需要是Functor类的实例。 在Monad库中对fmap的等效操作称为liftM

    liftM f mx = mx >>= x-> return (f x) 
    

    所以Monads Maybe的实际定义变成:

    return :: a -> Maybe a     
    return x = Just x
    
    (>>=)    :: Maybe a -> (a -> Maybe b) -> Maybe b
    Nothing >>= f = Nothing 
    Just x  >>= f = f x
    

    对于Monad []:

    return :: a -> [a]     
    return x = [x]
    
    (>>=)    :: [a] -> (a -> [b]) -> [b]
    xs >>= f = concat (map f xs)
             = concatMap f xs    -- same as above but more efficient
    

    在设计自己的Monads时,你可能会发现它更容易,而不是试图直接定义(>>=) ,将问题分成几部分,找出如何映射和加入结构。 通过映射和连接也可以用于验证Monad是否定义良好,即它符合所需的法律。

    Monad Laws

    你的Monad应该是一个Functor,所以映射操作应该满足:

    fmap id = id
    fmap g . fmap f = fmap (g . f)
    

    返回和加入的法律是:

    join . return      = id
    join . fmap return = id  
    join . join = join . fmap join 
    

    前两项法律规定合并撤销包装。 如果你在另一个容器中包装容器,加入会让你回到原来的位置。 如果您使用包装操作映射容器的内容,再次加入会让您回到最初的样子。 最后一条法则是加入的关联性。 如果您有三层容器,则通过从内部或外部合并获得相同的结果。

    再次,您可以使用bind而不是join和fmap。 你会减少,但(可以说)更复杂的法律:

    return a >>= f  = f a
    m >>= return    = m
    (m >>= f) >>= g = m >>= (x -> f x >>= g) 
    

    Haskell中的monad有两种操作定义:

    (>>=)  :: Monad m => m a -> (a -> m b) -> m b -- also called bind
    return :: Monad m => a -> m a
    

    如果你没有诀窍,那么这两项行动就需要满足某些法律,这些法律在这一点上可能会让你感到困惑。 从概念上讲,您使用绑定来操作单值级别的值,然后返回来创建来自“微不足道”值的单值值。 例如,

    getLine :: IO String

    所以你不能修改和putStrLn这个String - 因为它不是一个String而是一个IO String

    那么,我们有一个IO Monad方便,所以不用担心。 我们所要做的就是使用bind来做我们想要的。 让我们来看看IO Monad中的绑定:

    (>>=) :: IO a -> (a -> IO b) -> IO b
    

    如果我们把getLine放在绑定的左边,我们可以使它更加具体化。

    (>>=) :: IO String -> (String -> IO b) -> IO b
    

    好的,所以getLine >>= putStrLn . (++ ". No problem after all!") getLine >>= putStrLn . (++ ". No problem after all!")会打印输入的行并添加额外的内容。 右边是一个函数,它接受一个String并产生一个IO () - 这根本不难! 我们只是按类型走。

    Monad定义了许多不同的类型,例如Maybe[a] ,他们的行为概念相同。

    Just 2 >>= return . (+2) Just 2 >>= return . (+2)会产生Just 4 ,就像你所期望的那样。 请注意,我们必须在这里使用return ,因为否则右侧的函数将匹配返回类型mb ,而只是b ,这将是一个类型错误。 它在putStrLn的情况下工作,因为它已经产生了一些IO ,这正是我们需要匹配的类型。 (Spoiler:shape of foo >>= return . bar Monad是愚蠢的,因为每个Monad都是Functor ,你能弄清楚它的含义吗?)

    我个人认为,只要直觉能够让你了解monad的话题,如果你想深入探索,你确实需要深入理论。 我喜欢首先使用它们。 您可以查看各种Monad实例的源代码,例如Hoogle上的List( [] )Monad或Maybe Monad,并在精确实现方面稍微精明一些。 一旦你对此感到满意,就可以遵循实际的单子法,并尝试为他们获得更多理论上的理解!


    Typeclassopedia有关于Monad的章节(但是请先阅读关于FunctorApplicative的前面章节)。

    链接地址: http://www.djcxy.com/p/47679.html

    上一篇: Definition, Laws and Example

    下一篇: Is this a monad?