单子 - 定义,定律和例子

16 浏览
0 Comments

单子 - 定义,定律和例子

我正在学习函数式编程语言Haskell,并在学习解析器时遇到了Monad。我以前从未听说过它们,所以我做了一些额外的研究来弄清楚它们是什么。\n无论我到哪里学习这个主题,都只会更加困惑。我无法找到一个简单的定义来解释Monad是什么以及如何使用它们。\"Monad是一种通过值和计算序列来结构化计算的方式\" - 嗯??\n能否请有人给出一个简单的定义,解释在Haskell中Monad是什么,与之相关的规则,并给出一个例子?\n

    \n

  • 注意:我知道如何使用do语法,因为我已经研究了带有副作用的I/O操作和函数。
  • \n

0
0 Comments

问题的出现是因为在Typeclassopedia的Monad部分中,作者提到了前面关于Functor和Applicative的部分内容。

为了解决这个问题,我们需要先阅读关于Functor和Applicative的部分。

0
0 Comments

在Haskell中,一个Monad是指具有两个操作的东西:

(>>=) :: Monad m => m a -> (a -> m b) -> m b -- 也被称为bind

return :: Monad m => a -> m a

这两个操作需要满足一些法则,如果你对数学概念没有特别的感觉,那么这些法则可能会让你感到困惑。从概念上讲,你可以使用bind在单子级别上操作值,并使用return从“琐碎”的值创建单子值。例如,

getLine :: IO String

因此,你不能修改并且输出这个String--因为它不是一个String,而是一个IO String!

好吧,我们有一个IO Monad方便,所以不用担心。我们只需要使用bind来做我们想要的事情。让我们看看IO Monad中的bind是什么样子的:

(>>=) :: 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 something,这正是我们类型需要匹配的东西。(剧透:形如`foo >>= return . bar`的表达式是愚蠢的,因为每个Monad都是Functor。你能想出这意味着什么吗?)

我个人认为,对于Monad的直觉在这个问题上只能帮助你到这个程度,如果你想更深入地研究,你真的需要深入理论。我喜欢先熟悉它们的用法。你可以查找各种Monad实例的源代码,例如在Hoogle上查找List([]) Monad或Maybe Monad,并更深入地了解它们的具体实现。一旦你感到舒适,尝试一下实际的Monad法则,并试图对它们有一个更理论的理解!

作为旁注,我想补充一点,这并不算是一个Monad教程,所以当涉及到个人Monad教程时,我仍然是一个零分的人。

0
0 Comments

Monad - 定义、定律和示例

Monad是一种特殊的容器,它具有两种可用的操作。包装操作return将单个元素放入容器中,操作join将容器中的容器合并为一个容器。

对于Maybe 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

在Haskell中,Monad的标准数学定义基于return和join运算符。然而,在Haskell中,Monad类的定义使用绑定运算符替代了join。

在函数式编程语言中,这些特殊的容器通常用于表示具有效果的计算。类型Maybe a表示可能成功可能失败的计算,而类型[a]表示非确定性的计算。我们特别关注具有效果的函数,即具有类型a -> m b的函数,并且我们需要能够组合它们。这可以使用单子组合或绑定运算符来实现。

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

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

flip ($) :: a -> (a -> b) -> b

它接受一个具有效果的函数f :: a -> m b和一个返回类型为a的计算mx :: m a,并执行应用程序mx >>= f。那么我们如何使用Monad来实现这一点呢?容器(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类的实例。在Monad库中,与fmap等效的操作被称为liftM。

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

因此,Maybe Monad的实际定义变为:

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 -- 与上面相同,但更有效率

当设计自己的Monads时,你可能会发现,与其直接定义(>>=),不如将问题分解为部分,并找出如何映射和合并你的结构。具有映射和合并操作也可以用于验证你的Monad是否定义良好,即是否满足所需的定律。

Monad定律:

你的Monad应该是Functor,因此映射操作应满足:

fmap id = id

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

return和join的定律:

join . return = id

join . fmap return = id

join . join = join . fmap join

前两个定律指定合并操作撤销封装操作。如果你将一个容器包装到另一个容器中,join会给你原来的容器。如果你使用封装操作对容器的内容进行映射,join再次给你最初的内容。最后一个定律是join的结合性。如果你有三层容器,通过从内部或外部合并,你将得到相同的结果。

再次强调,你可以使用绑定运算符而不是join和fmap来工作。你会得到更少但(可以说)更复杂的定律:

return a >>= f = f a

m >>= return = m

(m >>= f) >>= g = m >>= (\x -> f x >>= g)

希望这个答案能够得到更多关注...基于join的定律让我开心,谢谢 🙂

0