可能重复:
What is a monad?
我正在学习使用Haskell的函数式语言进行编程,在学习解析器时遇到了 Monads 。我之前从未听说过这些,所以我做了一些额外的学习,以了解它们是什么。
为了学习这个话题我到处看,只是让我感到困惑。我真的找不到Monad是什么以及如何使用它们的简单定义。 “monad是一种使用这些值来计算值和计算顺序的计算方法” - 呃???
有人可以提供一个简单的定义Monad在Haskell中的含义,与之相关的法则并举例说明吗?
答案 0 :(得分:6)
粗略的直觉是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类的定义用于替换绑定运算符以进行连接。
在函数式编程语言中,这些特殊容器通常用于表示有效的计算。类型Maybe a
表示可能成功或可能不成功的计算,类型[a]
表示非确定性计算。特别是我们对具有效果的函数感兴趣,即某些a->m b
类型为Monad m
。我们需要能够组成它们。这可以使用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 -> m b
和一个计算mx :: m a
返回类型为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]
和一个非确定性函数f x = [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定义的绑定操作符也可用于定义映射操作。因此,Monads不需要是类Functor的实例。对于fmap的等效操作在Monad库中称为liftM
。
liftM f mx = mx >>= \x-> return (f x)
因此,Monads的实际定义可能变为:
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应该是一个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)
答案 1 :(得分:4)
Haskell中的monad是定义了两个操作的东西:
(>>=) :: Monad m => m a -> (a -> m b) -> m b -- also called bind
return :: Monad m => a -> m a
这两项操作需要满足某些法律,如果你对狡猾的想法没有诀窍,那么在这一点上真的可能会让你感到困惑。从概念上讲,您使用bind来操作monadic级别的值并返回以从“普通”值创建monadic值。例如,
getLine :: IO String
,
因此您无法修改putStrLn
此String
- 因为它不是String
而是IO String
!
好吧,我们有一个IO Monad方便,所以不用担心。我们所要做的就是使用bind来做我们想要的。让我们看看IO Monad中的绑定是什么样的:
(>>=) :: IO a -> (a -> IO b) -> IO b
如果我们将getLine
放在bind的左侧,我们可以使它更具体。
(>>=) :: IO String -> (String -> IO b) -> IO b
好的,所以getLine >>= putStrLn . (++ ". No problem after all!")
会打印输入的行,并添加额外的内容。右侧是一个需要String
并生成IO ()
的函数 - 这根本不难!我们只是选择类型。
为许多不同的类型定义了Monads,例如Maybe
和[a]
,并且它们在概念上以相同的方式运行。
Just 2 >>= return . (+2)
会产生Just 4
。请注意,我们必须在此处使用return
,否则右侧的函数不与返回类型m b
匹配,而只是b
,将是一个类型错误。它适用于putStrLn的情况,因为它已经产生了IO
的东西,这正是我们的类型需要匹配的东西。 (剧透:形状foo >>= return . bar
的表达是愚蠢的,因为每个Monad
都是Functor
。你能弄明白这意味着什么吗?)
我个人认为,只要直觉能让你了解monad的主题,如果你想深入探讨,你真的需要深入了解这个理论。我喜欢先使用它们。您可以查找各种Monad实例的源代码,例如Hoogle上的List([]
)Monad或Maybe
Monad,并在确切的实现方面更加智能。一旦你对此感到满意,就要了解实际的monad定律并尝试对它们有更多的理论认识!
答案 2 :(得分:2)
Typeclassopedia有一个关于Monad
的部分(但请先阅读前面有关Functor
和Applicative
的部分。)