什么是单子?

发布于 2024-07-04 01:01:51 字数 101 浏览 8 评论 0 原文

最近简要浏览了 Haskell,对于 monad 本质是什么,什么是简短、简洁、实用的解释?

我发现我遇到的大多数解释都相当难以理解并且缺乏实际细节。

Having briefly looked at Haskell recently, what would be a brief, succinct, practical explanation as to what a monad essentially is?

I have found most explanations I've come across to be fairly inaccessible and lacking in practical detail.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(30

红焚 2024-07-11 01:01:52

Monad 不是隐喻,而是从正如丹尼尔·斯皮瓦克(Daniel Spiewak)解释的那样,这是一种常见的模式。

Monads Are Not Metaphors, but a practically useful abstraction emerging from a common pattern, as Daniel Spiewak explains.

只想待在家 2024-07-11 01:01:52

除了上面的精彩答案之外,让我为您提供以下文章(作者:Patrick Thomson)的链接,该文章通过将概念与 JavaScript 库 jQuery 相关联来解释 monad(及其使用“方法”的方式)链接”来操作 DOM):
jQuery 是一个 Monad

jQuery 文档 本身并不引用术语“monad”但谈到可能更熟悉的“构建器模式”。 这并不会改变这样一个事实:你可能在没有意识到的情况下就有了一个合适的单子。

In addition to the excellent answers above, let me offer you a link to the following article (by Patrick Thomson) which explains monads by relating the concept to the JavaScript library jQuery (and its way of using "method chaining" to manipulate the DOM):
jQuery is a Monad

The jQuery documentation itself doesn't refer to the term "monad" but talks about the "builder pattern" which is probably more familiar. This doesn't change the fact that you have a proper monad there maybe without even realizing it.

暖心男生 2024-07-11 01:01:52

http://code.google.com/p/monad-tutorial/ 是一项正在进行的工作正是为了解决这个问题。

http://code.google.com/p/monad-tutorial/ is a work in progress to address exactly this question.

痴情换悲伤 2024-07-11 01:01:52

如果我理解正确的话,IEnumerable 是从 monad 派生的。 我想知道对于我们这些来自 C# 世界的人来说,这是否是一个有趣的方法角度?

无论如何,这里有一些对我有帮助的教程链接(不,我仍然不明白什么是 monad)。

If I've understood correctly, IEnumerable is derived from monads. I wonder if that might be an interesting angle of approach for those of us from the C# world?

For what it's worth, here are some links to tutorials that helped me (and no, I still haven't understood what monads are).

呆头 2024-07-11 01:01:52

世界需要的是另一篇 monad 博客文章,但我认为这对于识别现有的 monad 很有用。

谢尔宾斯基三角形

上面是一个称为谢尔宾斯基三角形的分形,这是我记得画的唯一分形。 分形是像上面的三角形一样的自相似结构,其中部分与整体相似(在这种情况下恰好是父三角形比例的一半)。

单子是分形的。 给定一个一元数据结构,它的值可以组合形成该数据结构的另一个值。 这就是它对编程有用的原因,也是它在许多情况下发生的原因。

What the world needs is another monad blog post, but I think this is useful in identifying existing monads in the wild.

Sierpinski triangle

The above is a fractal called Sierpinski triangle, the only fractal I can remember to draw. Fractals are self-similar structure like the above triangle, in which the parts are similar to the whole (in this case exactly half the scale as parent triangle).

Monads are fractals. Given a monadic data structure, its values can be composed to form another value of the data structure. This is why it's useful to programming, and this is why it occurrs in many situations.

慈悲佛祖 2024-07-11 01:01:52

monad 是一个容器,但是用于数据。 一个特殊的容器。

所有容器都可以有开口、把手和喷口,但这些容器都保证有一定的开口、把手和喷口。

为什么? 因为这些有保证的开口、把手和喷嘴对于以特定的、常见的方式拾取容器并将其连接在一起非常有用。

这使您可以拿起不同的容器,而无需对它们了解太多。 它还允许不同类型的容器轻松链接在一起。

A monad is a container, but for data. A special container.

All containers can have openings and handles and spouts, but these containers are all guaranteed to have certain openings and handles and spouts.

Why? Because these guaranteed openings and handles and spouts are useful for picking up and linking together the containers in specific, common ways.

This allows you to pick up different containers without having to know much about them. It also allows different kinds of containers to link together easily.

写给空气的情书 2024-07-11 01:01:52

Monad 是一个Applicative(即您可以提升二进制的东西 - 因此,“n-ary” -- 函数(1)并将纯值注入(2)Functor(即您可以映射< /em> 结束,(3) 即将一元函数提升为(3)),并增加了展平嵌套数据类型的能力 (三个概念中的每一个都遵循其相应的法则)。 在 Haskell 中,这种展平操作称为join

join操作的一般(通用、参数)类型是:

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

对于任何单子m(NB类型中的所有m都是相同的!)。

特定的 m monad 定义其特定版本的 join,适用于由 m a 类型的 Monadic 值“携带”的任何值类型 a 。 一些特定类型包括:

join  ::  [[a]]           -> [a]         -- for lists, or nondeterministic values
join  ::  Maybe (Maybe a) -> Maybe a     -- for Maybe, or optional values
join  ::  IO    (IO    a) -> IO    a     -- for I/O-produced values

join 操作转换 m 计算,生成 am 计算>-type 值 到a 类型值的一个组合m 计算中。 这允许将计算步骤组合成一个更大的计算。

计算步骤 - 组合“bind” (>>=) 运算符仅使用 fmapjoin在一起,即

(ma >>= k)  ==  join (fmap k ma)
{-
  ma        :: m a            -- `m`-computation which produces `a`-type values
  k         ::   a -> m b     --  create new `m`-computation from an `a`-type value
  fmap k ma :: m    ( m b )   -- `m`-computation of `m`-computation of `b`-type values
  (m >>= k) :: m        b     -- `m`-computation which produces `b`-type values
-}

相反,join可以通过bind定义,join mma == join(fmap id mma) == mma >>>= id 其中 id ma = ma -- 对于给定类型 m 来说哪个更方便。

对于 monad,do 表示法及其等效的 bind 使用代码都

do { x <- mx ; y <- my ; return (f x y) }        --   x :: a   ,   mx :: m a
                                                 --   y :: b   ,   my :: m b
mx >>= (\x ->                                    -- nested
            my >>= (\y ->                        --  lambda
                         return (f x y) ))       --   functions

可以读作

首先“执行”mx,完成后,将其“结果”作为x,让我用它来“执行”其他操作。

在给定的 do 块中,绑定箭头 <- 右侧的每个值的类型为 m a,对于某种类型 < code>a 和整个 do 块中相同的 monad m

return x 是一个中性的 m 计算,它只产生给定的纯值 x,这样就可以绑定任何 m code>-带有 return 的计算根本不会改变该计算。


(1)liftA2 :: Applicative m => (a→b→c)→ 妈-> mb-> m c

(2)pure :: Applicative m =>; 一个-> m a

(3)fmap :: Functor m => (a→b)→ 妈-> m b

还有等效的 Monad 方法,

liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c
return :: Monad m =>  a            -> m a
liftM  :: Monad m => (a -> b)      -> m a -> m b

给定一个 monad,其他定义可以为

pure   a       = return a
fmap   f ma    = do { a <- ma ;            return (f a)   }
liftA2 f ma mb = do { a <- ma ; b <- mb  ; return (f a b) }
(ma >>= k)     = do { a <- ma ; b <- k a ; return  b      }

A Monad is an Applicative (i.e. something that you can lift binary -- hence, "n-ary" -- functions to,(1) and inject pure values into(2)) Functor (i.e. something that you can map over,(3) i.e. lift unary functions to(3)) with the added ability to flatten the nested datatype (with each of the three notions following its corresponding set of laws). In Haskell, this flattening operation is called join.

The general (generic, parametric) type of this "join" operation is:

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

for any monad m (NB all ms in the type are the same!).

A specific m monad defines its specific version of join working for any value type a "carried" by the monadic values of type m a. Some specific types are:

join  ::  [[a]]           -> [a]         -- for lists, or nondeterministic values
join  ::  Maybe (Maybe a) -> Maybe a     -- for Maybe, or optional values
join  ::  IO    (IO    a) -> IO    a     -- for I/O-produced values

The join operation converts an m-computation producing an m-computation of a-type values into one combined m-computation of a-type values. This allows for combination of computation steps into one larger computation.

This computation steps-combining "bind" (>>=) operator simply uses fmap and join together, i.e.

(ma >>= k)  ==  join (fmap k ma)
{-
  ma        :: m a            -- `m`-computation which produces `a`-type values
  k         ::   a -> m b     --  create new `m`-computation from an `a`-type value
  fmap k ma :: m    ( m b )   -- `m`-computation of `m`-computation of `b`-type values
  (m >>= k) :: m        b     -- `m`-computation which produces `b`-type values
-}

Conversely, join can be defined via bind, join mma == join (fmap id mma) == mma >>= id where id ma = ma -- whichever is more convenient for a given type m.

For monads, both the do-notation and its equivalent bind-using code,

do { x <- mx ; y <- my ; return (f x y) }        --   x :: a   ,   mx :: m a
                                                 --   y :: b   ,   my :: m b
mx >>= (\x ->                                    -- nested
            my >>= (\y ->                        --  lambda
                         return (f x y) ))       --   functions

can be read as

first "do" mx, and when it's done, get its "result" as x and let me use it to "do" something else.

In a given do block, each of the values to the right of the binding arrow <- is of type m a for some type a and the same monad m throughout the do block.

return x is a neutral m-computation which just produces the pure value x it is given, such that binding any m-computation with return does not change that computation at all.


(1) with liftA2 :: Applicative m => (a -> b -> c) -> m a -> m b -> m c

(2) with pure :: Applicative m => a -> m a

(3) with fmap :: Functor m => (a -> b) -> m a -> m b

There's also the equivalent Monad methods,

liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c
return :: Monad m =>  a            -> m a
liftM  :: Monad m => (a -> b)      -> m a -> m b

Given a monad, the other definitions could be made as

pure   a       = return a
fmap   f ma    = do { a <- ma ;            return (f a)   }
liftA2 f ma mb = do { a <- ma ; b <- mb  ; return (f a b) }
(ma >>= k)     = do { a <- ma ; b <- k a ; return  b      }
近箐 2024-07-11 01:01:52

我将尝试在 Haskell 的上下文中解释 Monad

在函数式编程中,函数组合很重要。 它允许我们的程序由小的、易于阅读的函数组成。

假设我们有两个函数: g :: Int -> Stringf :: String -> 布尔。

我们可以执行 (f . g) x,这与 f (gx) 相同,其中 x 是一个 Int值。

当进行组合/将一个函数的结果应用到另一个函数时,类型匹配很重要。 在上述情况下,g返回的结果类型必须与f接受的类型相同。

但有时值位于上下文中,这使得排列类型变得不太容易。 (在上下文中拥有值非常有用。例如,Maybe Int 类型表示可能不存在的 Int 值,IO String type 表示一个 String 值,它是执行一些副作用的结果。)

假设我们现在有 g1 :: Int ->; 也许是 Stringf1 :: String -> 也许布尔。 g1f1 分别与 gf 非常相似。

我们不能执行 (f1 . g1) xf1 (g1 x),其中 xInt > 价值。 g1 返回的结果类型不是 f1 期望的类型。

我们可以使用 . 运算符组合 fg,但现在我们无法组合 f1g1.。 问题是我们不能直接将上下文中的值传递给需要上下文之外的值的函数。

如果我们引入一个运算符来组合 g1f1,这样我们就可以编写 (f1 OPERATOR g1) x,不是很好吗? g1 返回上下文中的值。 该值将脱离上下文并应用于 f1。 是的,我们有这样的操作员。 它是<=<

我们还有 >>= 运算符,它为我们做完全相同的事情,尽管语法略有不同。

我们写:g1 x >>= f1g1 x 是一个Maybe Int 值。 >>= 运算符有助于将 Int 值从“也许不存在”上下文中取出,并将其应用于 f1f1 的结果是一个 Maybe Bool,将是整个 >>= 运算的结果。

最后,为什么 Monad 有用? 因为 Monad 是定义 >>= 运算符的类型类,与定义 Eq 类型类非常相似。 code>== 和 /= 运算符。

总而言之,Monad 类型类定义了 >>= 运算符,该运算符允许我们将上下文中的值(我们称之为单子值)传递给不使用该运算符的函数。 t 期望上下文中的值。 上下文将得到照顾。

如果这里要记住一件事,那就是 Monad 允许函数组合涉及上下文中的值

I will try to explain Monad in the context of Haskell.

In functional programming, function composition is important. It allows our program to consist of small, easy-to-read functions.

Let's say we have two functions: g :: Int -> String and f :: String -> Bool.

We can do (f . g) x, which is just the same as f (g x), where x is an Int value.

When doing composition/applying the result of one function to another, having the types match up is important. In the above case, the type of the result returned by g must be the same as the type accepted by f.

But sometimes values are in contexts, and this makes it a bit less easy to line up types. (Having values in contexts is very useful. For example, the Maybe Int type represents an Int value that may not be there, the IO String type represents a String value that is there as a result of performing some side effects.)

Let's say we now have g1 :: Int -> Maybe String and f1 :: String -> Maybe Bool. g1 and f1 are very similar to g and f respectively.

We can't do (f1 . g1) x or f1 (g1 x), where x is an Int value. The type of the result returned by g1 is not what f1 expects.

We could compose f and g with the . operator, but now we can't compose f1 and g1 with .. The problem is that we can't straightforwardly pass a value in a context to a function that expects a value that is not in a context.

Wouldn't it be nice if we introduce an operator to compose g1 and f1, such that we can write (f1 OPERATOR g1) x? g1 returns a value in a context. The value will be taken out of context and applied to f1. And yes, we have such an operator. It's <=<.

We also have the >>= operator that does for us the exact same thing, though in a slightly different syntax.

We write: g1 x >>= f1. g1 x is a Maybe Int value. The >>= operator helps take that Int value out of the "perhaps-not-there" context, and apply it to f1. The result of f1, which is a Maybe Bool, will be the result of the entire >>= operation.

And finally, why is Monad useful? Because Monad is the type class that defines the >>= operator, very much the same as the Eq type class that defines the == and /= operators.

To conclude, the Monad type class defines the >>= operator that allows us to pass values in a context (we call these monadic values) to functions that don't expect values in a context. The context will be taken care of.

If there is one thing to remember here, it is that Monads allow function composition that involves values in contexts.

千仐 2024-07-11 01:01:52

这个答案从一个激励性的例子开始,通过这个例子,得出一个 monad 的例子,并正式定义“monad”。

考虑伪代码中的这三个函数:

f(<x, messages>) := <x, messages "called f. ">
g(<x, messages>) := <x, messages "called g. ">
wrap(x)          := <x, "">

f 采用 形式的有序对并返回一个有序对。 它保持第一个项目不变,并将 “叫 f.” 附加到第二个项目。 与g相同。

您可以组合这些函数并获取原始值,以及显示函数调用顺序的字符串:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<x, "called g. ">)
= <x, "called g. called f. ">

您不喜欢 fg 负责的事实将自己的日志消息附加到以前的日志信息中。 (试想一下,为了论证,fg 必须对这对中的第二项执行复杂的逻辑,而不是附加字符串。重复会很痛苦两个或多个不同函数中的复杂逻辑。)

您更喜欢编写更简单的函数:

f(x)    := <x, "called f. ">
g(x)    := <x, "called g. ">
wrap(x) := <x, "">

但是看看当您组合它们时会发生什么:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<<x, "">, "called g. ">)
= <<<x, "">, "called g. ">, "called f. ">

问题是将一对传递给函数不给你你想要的。 但是,如果您可以将一对喂入到一个函数中:

  feed(f, feed(g, wrap(x)))
= feed(f, feed(g, <x, "">))
= feed(f, <x, "called g. ">)
= <x, "called g. called f. ">

feed(f, m) 读为“将 m 喂入 f< /代码>”。 将一对喂入函数f就是传递x< /code> 进入 f,从 f 中获取 ,并返回

feed(f, <x, messages>) := let <y, message> = f(x)
                          in  <y, messages message>

请注意当您对函数执行三件事时会发生什么:

首先:如果您包装一个值,然后结果对输入到函数中:

  feed(f, wrap(x))
= feed(f, <x, "">)
= let <y, message> = f(x)
  in  <y, "" message>
= let <y, message> = <x, "called f. ">
  in  <y, "" message>
= <x, "" "called f. ">
= <x, "called f. ">
= f(x)

这与传递相同值到函数中。

其次:如果将一对输入到 wrap 中:

  feed(wrap, <x, messages>)
= let <y, message> = wrap(x)
  in  <y, messages message>
= let <y, message> = <x, "">
  in  <y, messages message>
= <x, messages "">
= <x, messages>

这不会改变该对。

第三:如果您定义一个函数,该函数接受 x 并将 g(x) 馈送到 f 中:

h(x) := feed(f, g(x))

并向其中馈送一对:

  feed(h, <x, messages>)
= let <y, message> = h(x)
  in  <y, messages message>
= let <y, message> = feed(f, g(x))
  in  <y, messages message>
= let <y, message> = feed(f, <x, "called g. ">)
  in  <y, messages message>
= let <y, message> = let <z, msg> = f(x)
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = let <z, msg> = <x, "called f. ">
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = <x, "called g. " "called f. ">
  in <y, messages message>
= <x, messages "called g. " "called f. ">
= feed(f, <x, messages "called g. ">)
= feed(f, feed(g, <x, messages>))

这就是与将这对输入到g并将结果对输入到f相同。

你拥有大部分单子。 现在您只需要了解程序中的数据类型即可。

是什么类型的值? 嗯,这取决于值 x 的类型。 如果x 的类型为t,那么您的pair 就是“pair of t and string”类型的值。 将该类型称为M t

M 是一个类型构造函数:M 单独并不引用类型,但是 M _ 一旦您填写空白就引用类型一种。 M int 是一个 int 和一个 string 的对。 M string 是一对字符串和一个字符串。 等等

恭喜,您已经创建了一个 monad!

正式而言,您的 monad 是元组

一个 monad 是一个元组 其中:

  • M 是一个类型构造函数。
  • feed 接受一个(接受 t 并返回 M u 的函数)和一个 M t 并返回M u
  • wrap 接受一个 v 并返回一个 M v

tuv 是任意三种类型,它们可能相同也可能不同。 monad 满足您为特定 monad 证明的三个属性:

  • 将包装的 t 提供给函数与传递相同将 t 解包到函数中。

    形式上:feed(f,wrap(x)) = f(x)

  • M t 喂入 wrap 不会对Mt

    形式上:feed(wrap, m) = m

  • M t(称为 m)输入到函数中

    • t 传递给 g
    • g 获取 M u(称之为 n
    • n 馈送到 f

    相同

    • m喂入g
    • g获取n
    • n喂入f

    形式上:feed(h, m) = feed(f, feed(g, m)) 其中 h(x) := feed(f, g(x))< /code>

通常,feed 称为 bind (在 Haskell 中又称为 >>=)并且 < code>wrap 称为return

This answer begins with a motivating example, works through the example, derives an example of a monad, and formally defines "monad".

Consider these three functions in pseudocode:

f(<x, messages>) := <x, messages "called f. ">
g(<x, messages>) := <x, messages "called g. ">
wrap(x)          := <x, "">

f takes an ordered pair of the form <x, messages> and returns an ordered pair. It leaves the first item untouched and appends "called f. " to the second item. Same with g.

You can compose these functions and get your original value, along with a string that shows which order the functions were called in:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<x, "called g. ">)
= <x, "called g. called f. ">

You dislike the fact that f and g are responsible for appending their own log messages to the previous logging information. (Just imagine for the sake of argument that instead of appending strings, f and g must perform complicated logic on the second item of the pair. It would be a pain to repeat that complicated logic in two -- or more -- different functions.)

You prefer to write simpler functions:

f(x)    := <x, "called f. ">
g(x)    := <x, "called g. ">
wrap(x) := <x, "">

But look at what happens when you compose them:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<<x, "">, "called g. ">)
= <<<x, "">, "called g. ">, "called f. ">

The problem is that passing a pair into a function does not give you what you want. But what if you could feed a pair into a function:

  feed(f, feed(g, wrap(x)))
= feed(f, feed(g, <x, "">))
= feed(f, <x, "called g. ">)
= <x, "called g. called f. ">

Read feed(f, m) as "feed m into f". To feed a pair <x, messages> into a function f is to pass x into f, get <y, message> out of f, and return <y, messages message>.

feed(f, <x, messages>) := let <y, message> = f(x)
                          in  <y, messages message>

Notice what happens when you do three things with your functions:

First: if you wrap a value and then feed the resulting pair into a function:

  feed(f, wrap(x))
= feed(f, <x, "">)
= let <y, message> = f(x)
  in  <y, "" message>
= let <y, message> = <x, "called f. ">
  in  <y, "" message>
= <x, "" "called f. ">
= <x, "called f. ">
= f(x)

That is the same as passing the value into the function.

Second: if you feed a pair into wrap:

  feed(wrap, <x, messages>)
= let <y, message> = wrap(x)
  in  <y, messages message>
= let <y, message> = <x, "">
  in  <y, messages message>
= <x, messages "">
= <x, messages>

That does not change the pair.

Third: if you define a function that takes x and feeds g(x) into f:

h(x) := feed(f, g(x))

and feed a pair into it:

  feed(h, <x, messages>)
= let <y, message> = h(x)
  in  <y, messages message>
= let <y, message> = feed(f, g(x))
  in  <y, messages message>
= let <y, message> = feed(f, <x, "called g. ">)
  in  <y, messages message>
= let <y, message> = let <z, msg> = f(x)
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = let <z, msg> = <x, "called f. ">
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = <x, "called g. " "called f. ">
  in <y, messages message>
= <x, messages "called g. " "called f. ">
= feed(f, <x, messages "called g. ">)
= feed(f, feed(g, <x, messages>))

That is the same as feeding the pair into g and feeding the resulting pair into f.

You have most of a monad. Now you just need to know about the data types in your program.

What type of value is <x, "called f. ">? Well, that depends on what type of value x is. If x is of type t, then your pair is a value of type "pair of t and string". Call that type M t.

M is a type constructor: M alone does not refer to a type, but M _ refers to a type once you fill in the blank with a type. An M int is a pair of an int and a string. An M string is a pair of a string and a string. Etc.

Congratulations, you have created a monad!

Formally, your monad is the tuple <M, feed, wrap>.

A monad is a tuple <M, feed, wrap> where:

  • M is a type constructor.
  • feed takes a (function that takes a t and returns an M u) and an M t and returns an M u.
  • wrap takes a v and returns an M v.

t, u, and v are any three types that may or may not be the same. A monad satisfies the three properties you proved for your specific monad:

  • Feeding a wrapped t into a function is the same as passing the unwrapped t into the function.

    Formally: feed(f, wrap(x)) = f(x)

  • Feeding an M t into wrap does nothing to the M t.

    Formally: feed(wrap, m) = m

  • Feeding an M t (call it m) into a function that

    • passes the t into g
    • gets an M u (call it n) from g
    • feeds n into f

    is the same as

    • feeding m into g
    • getting n from g
    • feeding n into f

    Formally: feed(h, m) = feed(f, feed(g, m)) where h(x) := feed(f, g(x))

Typically, feed is called bind (AKA >>= in Haskell) and wrap is called return.

冷情 2024-07-11 01:01:52

monad 是将共享公共上下文的计算组合在一起的一种方式。 这就像建立一个管道网络。 构建网络时,没有数据流经其中。 但是,当我用“bind”和“return”将所有位拼凑在一起后,我会调用诸如 runMyMonad monad data 之类的东西,并且数据流经管道。

A monad is a way of combining computations together that share a common context. It is like building a network of pipes. When constructing the network, there is no data flowing through it. But when I have finished piecing all the bits together with 'bind' and 'return' then I invoke something like runMyMonad monad data and the data flows through the pipes.

待"谢繁草 2024-07-11 01:01:52

实际上,monad 是函数组合运算符的自定义实现,它负责处理副作用以及不兼容的输入和返回值(用于链接)。

In practice, monad is a custom implementation of function composition operator that takes care of side effects and incompatible input and return values (for chaining).

倒数 2024-07-11 01:01:52

在学习时对我帮助最大的两件事是:

第 8 章“函数解析器”,来自 Graham Hutton 的书 Haskell 编程。 实际上,这根本没有提到 monad,但是如果您可以完成本章并真正理解其中的所有内容,特别是如何评估绑定操作序列,您就会了解 monad 的内部结构。 预计这需要多次尝试。

本教程关于 Monad 的一切。 这给出了它们使用的几个很好的例子,我不得不说附录中的类比对我来说很有效。

The two things that helped me best when learning about there were:

Chapter 8, "Functional Parsers," from Graham Hutton's book Programming in Haskell. This doesn't mention monads at all, actually, but if you can work through chapter and really understand everything in it, particularly how a sequence of bind operations is evaluated, you'll understand the internals of monads. Expect this to take several tries.

The tutorial All About Monads. This gives several good examples of their use, and I have to say that the analogy in Appendex I worked for me.

浅浅淡淡 2024-07-11 01:01:52

Monoid 似乎可以确保在 Monoid 和受支持的类型上定义的所有操作将始终返回 Monoid 内受支持的类型。 例如,任意数字 + 任意数字 = 一个数字,没有错误。

而除法接受两个小数,并返回一个小数,它在 haskell 中将除以零定义为无穷大(不知何故恰好是小数)...

无论如何,Monads 似乎只是确保你的链的一种方法操作以可预测的方式运行,并且函数声称是 Num -> Num,由另一个函数Num->Num组成,用x调用不说,发射导弹。

另一方面,如果我们有一个可以发射导弹的函数,我们可以将它与其他也可以发射导弹的函数组合起来,因为我们的意图很明确——我们想要发射导弹——但它不会尝试由于某些奇怪的原因打印“Hello World”。

在 Haskell 中,main 是 IO () 或 IO [()] 类型,这种区别很奇怪,我不会讨论它,但我认为会发生以下情况:

如果我有 main,我希望它执行一系列操作,我运行程序的原因是为了产生效果——通常是通过 IO。 因此,我可以在 main 中将 IO 操作链接在一起,以便执行 IO,仅此而已。

如果我尝试做一些不“返回 IO”的事情,程序会抱怨链不流动,或者基本上“这与我们正在尝试做的事情有什么关系——一个 IO 操作”,它似乎强制程序员要保持自己的思路,不要偏离目标,想着发射导弹,同时创建排序算法——这并不流畅。

基本上,Monad 似乎是给编译器的一个提示:“嘿,你知道这个函数在这里返回一个数字,它实际上并不总是有效,它有时会产生一个数字,有时什么也不会产生,只需将其保留在头脑”。 知道这一点,如果您尝试断言一元操作,该一元操作可能会充当编译时异常,说“嘿,这实际上不是一个数字,这可以是一个数字,但您不能假设这一点,做一些事情以确保流量是可以接受的。” 这在一定程度上防止了不可预测的程序行为。

看来单子与纯度无关,也与控制无关,而是与维护一个类别的身份有关,在该类别上所有行为都是可预测和定义的,或者不能编译。 当你被期望做某事时,你不能什么都不做,而如果你被期望什么都不做,你也不能做某事(可见)。

我能想到的 Monad 的最大原因是——看看过程/OOP 代码,你会发现你不知道程序从哪里开始,也不知道结束,你看到的只是大量的跳跃和大量的数学运算、魔法和导弹。 您将无法维护它,如果可以的话,您将花费大量时间来思考整个程序,然后才能理解它的任何部分,因为这种情况下的模块化是基于相互依赖的“部分”代码,其中代码被优化为尽可能相关,以保证效率/相互关系。 Monad 非常具体,并且根据定义进行了明确定义,并确保可以分析程序流程,并隔离难以分析的部分 - 因为它们本身就是 monad。 一个单子似乎是一个“可理解的单元,在充分理解后是可预测的”——如果你理解“也许”单子,除了“也许”之外,它不可能做任何事情,这看起来微不足道,但在大多数非单子中在代码中,一个简单的函数“helloworld”可以发射导弹,什么也不做,或者摧毁宇宙,甚至扭曲时间——我们不知道也不能保证它就是它是什么。 一个 monad 保证它就是它本来的样子。 这是非常强大的。

“现实世界”中的所有事物似乎都是单子,因为它受到明确的可观察法则的约束,以防止混乱。 这并不意味着我们必须模仿这个对象的所有操作来创建类,而是我们可以简单地说“正方形就是正方形”,只不过是一个正方形,甚至不是矩形或圆形,并且“正方形有面积”它现有维度之一的长度乘以自身,无论你有什么正方形,如果它是二维空间中的正方形,它的面积绝对不能是它的长度的平方,证明这几乎是微不足道的,因为这是非常强大的。我们不需要做出断言来确保我们的世界是这样的,我们只是利用现实的影响来防止我们的计划偏离正轨,

但我认为这可以帮助那里的人。 ,所以希望它对某人有帮助。

Monoid appears to be something that ensures that all operations defined on a Monoid and a supported type will always return a supported type inside the Monoid. Eg, Any number + Any number = A number, no errors.

Whereas division accepts two fractionals, and returns a fractional, which defined division by zero as Infinity in haskell somewhy(which happens to be a fractional somewhy)...

In any case, it appears Monads are just a way to ensure that your chain of operations behaves in a predictable way, and a function that claims to be Num -> Num, composed with another function of Num->Num called with x does not say, fire the missiles.

On the other hand, if we have a function which does fire the missiles, we can compose it with other functions which also fire the missiles, because our intent is clear -- we want to fire the missiles -- but it won't try printing "Hello World" for some odd reason.

In Haskell, main is of type IO (), or IO [()], the distiction is strange and I will not discuss it but here's what I think happens:

If I have main, I want it to do a chain of actions, the reason I run the program is to produce an effect -- usually though IO. Thus I can chain IO operations together in main in order to -- do IO, nothing else.

If I try to do something which does not "return IO", the program will complain that the chain does not flow, or basically "How does this relate to what we are trying to do -- an IO action", it appears to force the programmer to keep their train of thought, without straying off and thinking about firing the missiles, while creating algorithms for sorting -- which does not flow.

Basically, Monads appear to be a tip to the compiler that "hey, you know this function that returns a number here, it doesn't actually always work, it can sometimes produce a Number, and sometimes Nothing at all, just keep this in mind". Knowing this, if you try to assert a monadic action, the monadic action may act as a compile time exception saying "hey, this isn't actually a number, this CAN be a number, but you can't assume this, do something to ensure that the flow is acceptable." which prevents unpredictable program behavior -- to a fair extent.

It appears monads are not about purity, nor control, but about maintaining an identity of a category on which all behavior is predictable and defined, or does not compile. You cannot do nothing when you are expected to do something, and you cannot do something if you are expected to do nothing (visible).

The biggest reason I could think of for Monads is -- go look at Procedural/OOP code, and you will notice that you do not know where the program starts, nor ends, all you see is a lot of jumping and a lot of math,magic,and missiles. You will not be able to maintain it, and if you can, you will spend quite a lot of time wrapping your mind around the whole program before you can understand any part of it, because modularity in this context is based on interdependant "sections" of code, where code is optimized to be as related as possible for promise of efficiency/inter-relation. Monads are very concrete, and well defined by definition, and ensure that the flow of program is possible to analyze, and isolate parts which are hard to analyze -- as they themselves are monads. A monad appears to be a "comprehensible unit which is predictable upon its full understanding" -- If you understand "Maybe" monad, there's no possible way it will do anything except be "Maybe", which appears trivial, but in most non monadic code, a simple function "helloworld" can fire the missiles, do nothing, or destroy the universe or even distort time -- we have no idea nor have any guarantees that IT IS WHAT IT IS. A monad GUARANTEES that IT IS WHAT IT IS. which is very powerful.

All things in "real world" appear to be monads, in the sense that it is bound by definite observable laws preventing confusion. This does not mean we have to mimic all the operations of this object to create classes, instead we can simply say "a square is a square", nothing but a square, not even a rectangle nor a circle, and "a square has area of the length of one of it's existing dimensions multiplied by itself. No matter what square you have, if it's a square in 2D space, it's area absolutely cannot be anything but its length squared, it's almost trivial to prove. This is very powerful because we do not need to make assertions to make sure that our world is the way it is, we just use implications of reality to prevent our programs from falling off track.

Im pretty much guaranteed to be wrong but I think this could help somebody out there, so hopefully it helps somebody.

任谁 2024-07-11 01:01:52

在 Scala 上下文中,您会发现以下是最简单的定义。 基本上,flatMap(或绑定)是“关联的”并且存在一个标识。

trait M[+A] {
  def flatMap[B](f: A => M[B]): M[B] // AKA bind

  // Pseudo Meta Code
  def isValidMonad: Boolean = {
    // for every parameter the following holds
    def isAssociativeOn[X, Y, Z](x: M[X], f: X => M[Y], g: Y => M[Z]): Boolean =
      x.flatMap(f).flatMap(g) == x.flatMap(f(_).flatMap(g))

    // for every parameter X and x, there exists an id
    // such that the following holds
    def isAnIdentity[X](x: M[X], id: X => M[X]): Boolean =
      x.flatMap(id) == x
  }
}

例如

// These could be any functions
val f: Int => Option[String] = number => if (number == 7) Some("hello") else None
val g: String => Option[Double] = string => Some(3.14)

// Observe these are identical. Since Option is a Monad 
// they will always be identical no matter what the functions are
scala> Some(7).flatMap(f).flatMap(g)
res211: Option[Double] = Some(3.14)

scala> Some(7).flatMap(f(_).flatMap(g))
res212: Option[Double] = Some(3.14)


// As Option is a Monad, there exists an identity:
val id: Int => Option[Int] = x => Some(x)

// Observe these are identical
scala> Some(7).flatMap(id)
res213: Option[Int] = Some(7)

scala> Some(7)
res214: Some[Int] = Some(7)

注意严格来说函数式编程中的Monad的定义 与范畴论中 Monad 的定义不同,依次定义mapflatten。 尽管它们在某些映射下是等效的。 这个演示文稿非常好: http://www.slideshare.net /samthemonad/monad-presentation-scala-as-a-category

In the context of Scala you will find the following to be the simplest definition. Basically flatMap (or bind) is 'associative' and there exists an identity.

trait M[+A] {
  def flatMap[B](f: A => M[B]): M[B] // AKA bind

  // Pseudo Meta Code
  def isValidMonad: Boolean = {
    // for every parameter the following holds
    def isAssociativeOn[X, Y, Z](x: M[X], f: X => M[Y], g: Y => M[Z]): Boolean =
      x.flatMap(f).flatMap(g) == x.flatMap(f(_).flatMap(g))

    // for every parameter X and x, there exists an id
    // such that the following holds
    def isAnIdentity[X](x: M[X], id: X => M[X]): Boolean =
      x.flatMap(id) == x
  }
}

E.g.

// These could be any functions
val f: Int => Option[String] = number => if (number == 7) Some("hello") else None
val g: String => Option[Double] = string => Some(3.14)

// Observe these are identical. Since Option is a Monad 
// they will always be identical no matter what the functions are
scala> Some(7).flatMap(f).flatMap(g)
res211: Option[Double] = Some(3.14)

scala> Some(7).flatMap(f(_).flatMap(g))
res212: Option[Double] = Some(3.14)


// As Option is a Monad, there exists an identity:
val id: Int => Option[Int] = x => Some(x)

// Observe these are identical
scala> Some(7).flatMap(id)
res213: Option[Int] = Some(7)

scala> Some(7)
res214: Some[Int] = Some(7)

NOTE Strictly speaking the definition of a Monad in functional programming is not the same as the definition of a Monad in Category Theory, which is defined in turns of map and flatten. Though they are kind of equivalent under certain mappings. This presentations is very good: http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category

鸩远一方 2024-07-11 01:01:52

实际上,单子是“类型运算符”的一种形式。 它将做三件事。 首先,它将一种类型的值“包装”(或以其他方式转换)为另一种类型(通常称为“单子类型”)。 其次,它将使基础类型上可用的所有操作(或函数)在单子类型上可用。 最后,它将提供将自身与另一个 monad 组合以生成复合 monad 的支持。

“maybe monad”本质上相当于 Visual Basic / C# 中的“可空类型”。 它采用不可为 null 的类型“T”并将其转换为“Nullable”,然后定义所有二元运算符在 Nullable 上的含义。

副作用也有类似的表现。 创建一个结构来保存副作用的描述以及函数的返回值。 然后,当值在函数之间传递时,“提升”操作会复制副作用。

它们被称为“monad”,而不是更容易理解的“类型运算符”名称,原因如下:

  1. Monad 对其功能有限制(有关详细信息,请参阅定义)。
  2. 这些限制以及涉及三个操作的事实符合范畴论中称为单子的结构,范畴论是数学的一个晦涩分支。
  3. 它们是由“纯”函数式语言的支持者设计的
  4. 纯函数式语言的支持者就像晦涩的数学分支
  5. 由于数学晦涩难懂,而 monad 与特定的编程风格相关,人们倾向于使用 monad 这个词作为一种秘密握手。 正因为如此,没有人费心去投资一个更好的名字。

A monad is, effectively, a form of "type operator". It will do three things. First it will "wrap" (or otherwise convert) a value of one type into another type (typically called a "monadic type"). Secondly it will make all the operations (or functions) available on the underlying type available on the monadic type. Finally it will provide support for combining its self with another monad to produce a composite monad.

The "maybe monad" is essentially the equivalent of "nullable types" in Visual Basic / C#. It takes a non nullable type "T" and converts it into a "Nullable<T>", and then defines what all the binary operators mean on a Nullable<T>.

Side effects are represented simillarly. A structure is created that holds descriptions of side effects alongside a function's return value. The "lifted" operations then copy around side effects as values are passed between functions.

They are called "monads" rather than the easier-to-grasp name of "type operators" for several reasons:

  1. Monads have restrictions on what they can do (see the definiton for details).
  2. Those restrictions, along with the fact that there are three operations involved, conform to the structure of something called a monad in Category Theory, which is an obscure branch of mathematics.
  3. They were designed by proponents of "pure" functional languages
  4. Proponents of pure functional languages like obscure branches of mathematics
  5. Because the math is obscure, and monads are associated with particular styles of programming, people tend to use the word monad as a sort of secret handshake. Because of this no one has bothered to invest in a better name.
埋葬我深情 2024-07-11 01:01:52

Monad 控制流程就像抽象数据类型控制数据一样。

换句话说,许多开发人员都对集合、列表、字典(或哈希或映射)和树的概念感到满意。 在这些数据类型中,有许多特殊情况(例如 InsertionOrderPreservingIdentityHashMap)。

然而,当面对程序“流程”时,许多开发人员还没有接触过比 if、switch/case、do、while、goto (grr) 和(可能)闭包更多的结构。

因此,单子只是一个控制流结构。 替换 monad 的更好短语是“控制类型”。

因此,单子具有用于控制逻辑、语句或函数的槽——数据结构中的等价物是说某些数据结构允许您添加数据和删除数据。

例如,“if”单子:

if( clause ) then block

最简单的情况是有两个槽 - 一个子句和一个块。 if monad 通常用于评估子句的结果,如果不为 false,则评估块。 许多开发人员在学习“if”时并没有接触到 monad,而且没有必要理解 monad 来编写有效的逻辑。

Monad 可以变得更加复杂,就像数据结构可以变得更加复杂一样,但是有许多大类的 monad 可能具有相似的语义,但具有不同的实现和语法。

当然,以与可以迭代或遍历数据结构相同的方式,可以评估单子。

编译器可能支持也可能不支持用户定义的 monad。 哈斯克尔确实如此。 Ioke 具有一些类似的功能,尽管该语言中未使用术语 monad。

Monads are to control flow what abstract data types are to data.

In other words, many developers are comfortable with the idea of Sets, Lists, Dictionaries (or Hashes, or Maps), and Trees. Within those data types there are many special cases (for instance InsertionOrderPreservingIdentityHashMap).

However, when confronted with program "flow" many developers haven't been exposed to many more constructs than if, switch/case, do, while, goto (grr), and (maybe) closures.

So, a monad is simply a control flow construct. A better phrase to replace monad would be 'control type'.

As such, a monad has slots for control logic, or statements, or functions - the equivalent in data structures would be to say that some data structures allow you to add data, and remove it.

For example, the "if" monad:

if( clause ) then block

at its simplest has two slots - a clause, and a block. The if monad is usually built to evaluate the result of the clause, and if not false, evaluate the block. Many developers are not introduced to monads when they learn 'if', and it just isn't necessary to understand monads to write effective logic.

Monads can become more complicated, in the same way that data structures can become more complicated, but there are many broad categories of monad that may have similar semantics, but differing implementations and syntax.

Of course, in the same way that data structures may be iterated over, or traversed, monads may be evaluated.

Compilers may or may not have support for user-defined monads. Haskell certainly does. Ioke has some similar capabilities, although the term monad is not used in the language.

野鹿林 2024-07-11 01:01:52

(另请参阅什么是 monad? 的答案)

Monads 的一个很好的动机是 sigfpe (Dan Piponi) 的 你本可以发明 Monad! (也许你已经有了)。 有很多其他 monad 教程,其中许多错误地尝试用“简单的术语”来解释 monad ” 使用各种类比:这是 Monad 教程谬误; 避开他们。

正如 MacIver 博士在告诉我们为什么你的语言很糟糕

所以,我讨厌 Haskell 的事情:

让我们从显而易见的事情开始。 莫纳德教程。 不,不是单子。 具体教程。 它们是无穷无尽的,夸张的,亲爱的上帝,它们太乏味了。 此外,我从未见过任何令人信服的证据表明它们确实有帮助。 阅读类定义,编写一些代码,克服可怕的名称。

你说你了解 Maybe monad 吗? 很好,你已经在路上了。 只要开始使用其他 monad,迟早您就会了解 monad 的一般含义。

[如果您是数学导向的,您可能想忽略数十个教程并学习定义,或遵循 范畴论讲座:)
定义的主要部分是 Monad M 涉及一个“类型构造函数”,它为每个现有类型“T”定义一个新类型“M T”,以及在“常规”类型和“M”之间来回切换的一些方法]

另外,令人惊讶的是,对 monad 最好的介绍之一实际上是介绍 monad 的早期学术论文之一,Philip Wadler 的 函数式编程的 Monad。 它实际上有实用的、非平凡的激励示例,与许多人工教程不同。

(See also the answers at What is a monad?)

A good motivation to Monads is sigfpe (Dan Piponi)'s You Could Have Invented Monads! (And Maybe You Already Have). There are a LOT of other monad tutorials, many of which misguidedly try to explain monads in "simple terms" using various analogies: this is the monad tutorial fallacy; avoid them.

As DR MacIver says in Tell us why your language sucks:


So, things I hate about Haskell:

Let’s start with the obvious. Monad tutorials. No, not monads. Specifically the tutorials. They’re endless, overblown and dear god are they tedious. Further, I’ve never seen any convincing evidence that they actually help. Read the class definition, write some code, get over the scary name.

You say you understand the Maybe monad? Good, you're on your way. Just start using other monads and sooner or later you'll understand what monads are in general.

[If you are mathematically oriented, you might want to ignore the dozens of tutorials and learn the definition, or follow lectures in category theory :)
The main part of the definition is that a Monad M involves a "type constructor" that defines for each existing type "T" a new type "M T", and some ways for going back and forth between "regular" types and "M" types.]

Also, surprisingly enough, one of the best introductions to monads is actually one of the early academic papers introducing monads, Philip Wadler's Monads for functional programming. It actually has practical, non-trivial motivating examples, unlike many of the artificial tutorials out there.

执妄 2024-07-11 01:01:52

我最喜欢的 Monad 教程:

http://www.haskell.org/haskellwiki/All_About_Monads

(来自Google 搜索“monad 教程”有 170,000 次点击!)

@Stu:monad 的要点是允许您向其他纯代码添加(通常)顺序语义; 您甚至可以编写 monad(使用 Monad Transformers)并获得更有趣和更复杂的组合语义,例如使用错误处理、共享状态和日志记录进行解析。 所有这些都可以在纯代码中实现,monad 只是允许您将其抽象出来并在模块化库中重用它(在编程中总是很好),并提供方便的语法以使其看起来命令式。

Haskell 已经具有运算符重载 [1]:它使用类型类的方式与使用 Java 或 C# 中的接口的方式非常相似,但 Haskell 恰好也允许非字母数字标记,例如 + && 和> 作为中缀标识符。 如果您的意思是“重载分号”[2],那么这只是您看待它的方式中的运算符重载。 这听起来像是黑魔法,自找麻烦来“超载分号”(想象一下有进取心的 Perl 黑客听到了这个想法),但重点是,没有 monads 就没有分号,因为纯粹的函数式代码不需要或不允许显式排序。

这一切听起来比实际需要的复杂得多。 sigfpe 的文章很酷,但是使用 Haskell 来解释它,这有点无法打破理解 Haskell 到 grok Monads 以及理解 Monads 到 grok Haskell 的先有鸡还是先有蛋的问题。

[1] 这是与 monad 不同的问题,但 monad 使用 Haskell 的运算符重载功能。

[2] 这也是一种过度简化,因为用于链接一元操作的运算符是 >>= (发音为“bind”),但有语法糖(“do”)允许您使用大括号和分号和/或缩进和换行符。

My favorite Monad tutorial:

http://www.haskell.org/haskellwiki/All_About_Monads

(out of 170,000 hits on a Google search for "monad tutorial"!)

@Stu: The point of monads is to allow you to add (usually) sequential semantics to otherwise pure code; you can even compose monads (using Monad Transformers) and get more interesting and complicated combined semantics, like parsing with error handling, shared state, and logging, for example. All of this is possible in pure code, monads just allow you to abstract it away and reuse it in modular libraries (always good in programming), as well as providing convenient syntax to make it look imperative.

Haskell already has operator overloading[1]: it uses type classes much the way one might use interfaces in Java or C# but Haskell just happens to also allow non-alphanumeric tokens like + && and > as infix identifiers. It's only operator overloading in your way of looking at it if you mean "overloading the semicolon" [2]. It sounds like black magic and asking for trouble to "overload the semicolon" (picture enterprising Perl hackers getting wind of this idea) but the point is that without monads there is no semicolon, since purely functional code does not require or allow explicit sequencing.

This all sounds much more complicated than it needs to. sigfpe's article is pretty cool but uses Haskell to explain it, which sort of fails to break the chicken and egg problem of understanding Haskell to grok Monads and understanding Monads to grok Haskell.

[1] This is a separate issue from monads but monads use Haskell's operator overloading feature.

[2] This is also an oversimplification since the operator for chaining monadic actions is >>= (pronounced "bind") but there is syntactic sugar ("do") that lets you use braces and semicolons and/or indentation and newlines.

旧时浪漫 2024-07-11 01:01:52

我对 monad 还很陌生,但我想我应该分享一个我发现读起来感觉很好的链接(带图片!!):
http://www.matusiak.eu/ numerodix/blog/2012/3/11/monads-for-the-layman/
(无隶属关系)

基本上,我从这篇文章中得到的温暖而模糊的概念是,单子基本上是适配器,允许不同的函数以可组合的方式工作,即能够串联多个函数并混合和匹配它们,而无需担心返回类型不一致等。 因此,当我们尝试制作这些适配器时,BIND 函数负责让苹果与苹果、橙子与橙子保持一致。 LIFT 函数负责获取“较低级别”函数并“升级”它们以与 BIND 函数一起使用并且也可组合。

我希望我是对的,更重要的是,希望这篇文章对 monad 有一个有效的观点。 如果不出意外的话,这篇文章激发了我更多地了解 monad 的兴趣。

I am still new to monads, but I thought I would share a link I found that felt really good to read (WITH PICTURES!!):
http://www.matusiak.eu/numerodix/blog/2012/3/11/monads-for-the-layman/
(no affiliation)

Basically, the warm and fuzzy concept that I got from the article was the concept that monads are basically adapters that allow disparate functions to work in a composable fashion, i.e. be able to string up multiple functions and mix and match them without worrying about inconsistent return types and such. So the BIND function is in charge of keeping apples with apples and oranges with oranges when we're trying to make these adapters. And the LIFT function is in charge of taking "lower level" functions and "upgrading" them to work with BIND functions and be composable as well.

I hope I got it right, and more importantly, hope that the article has a valid view on monads. If nothing else, this article helped whet my appetite for learning more about monads.

乖乖 2024-07-11 01:01:52

最近我一直在以不同的方式思考 Monad。 我一直认为它们以数学方式抽象出执行顺序,这使得新型多态性成为可能。

如果您使用命令式语言,并且按顺序编写一些表达式,则代码始终完全按照该顺序运行。

在简单的情况下,当你使用 monad 时,感觉是一样的——你定义了一个按顺序发生的表达式列表。 除此之外,根据您使用的 monad,您的代码可能会按顺序运行(如在 IO monad 中),同时在多个项目上并行运行(如在 List monad 中),它可能会中途停止(如在 Maybe monad 中) ,它可能会中途暂停以稍后恢复(如在恢复 monad 中),它可能会倒回并从头开始(如在事务 monad 中),或者它可能会中途倒回以尝试其他选项(如在逻辑 monad 中) 。

而且由于 monad 是多态的,因此可以根据您的需要在不同的 monad 中运行相同的代码。

另外,在某些情况下,可以将 monad 组合在一起(使用 monad 转换器)以同时获得多个功能。

I've been thinking of Monads in a different way, lately. I've been thinking of them as abstracting out execution order in a mathematical way, which makes new kinds of polymorphism possible.

If you're using an imperative language, and you write some expressions in order, the code ALWAYS runs exactly in that order.

And in the simple case, when you use a monad, it feels the same -- you define a list of expressions that happen in order. Except that, depending on which monad you use, your code might run in order (like in IO monad), in parallel over several items at once (like in the List monad), it might halt partway through (like in the Maybe monad), it might pause partway through to be resumed later (like in a Resumption monad), it might rewind and start from the beginning (like in a Transaction monad), or it might rewind partway to try other options (like in a Logic monad).

And because monads are polymorphic, it's possible to run the same code in different monads, depending on your needs.

Plus, in some cases, it's possible to combine monads together (with monad transformers) to get multiple features at the same time.

温柔戏命师 2024-07-11 01:01:52

tl;dr

{-# LANGUAGE InstanceSigs #-}

newtype Id t = Id t

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

序言

函数的应用运算符 $

forall a b. a -> b

规范定义的

($) :: (a -> b) -> a -> b
f $ x = f x

infixr 0 $

是根据 Haskell 基元函数应用 f x (infixl 10)

。 组合 . 是根据 $ 定义的,

(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x

infixr 9 .

并满足等价 forall fg h.

     f . id  =  f            :: c -> d   Right identity
     id . g  =  g            :: b -> c   Left identity
(f . g) . h  =  f . (g . h)  :: a -> d   Associativity

. 是关联的,并且id 是它的左右标识。

Kleisli 三元组

在编程中,monad 是一个函子类型构造函数,具有 monad 类型类的实例。 有几个等效的定义和实现变体,每个变体对 monad 抽象的直觉略有不同。

函子是 * -> 类型的类型构造函数 f。 * 带有仿函数类型类的实例。

{-# LANGUAGE KindSignatures #-}

class Functor (f :: * -> *) where
   map :: (a -> b) -> (f a -> f b)

除了遵循静态强制类型协议之外,函子类型类的实例还必须遵守代数函子定律 forall f g。

       map id  =  id           :: f t -> f t   Identity
map f . map g  =  map (f . g)  :: f a -> f c   Composition / short cut fusion

函子计算具有类型

forall f t. Functor f => f t

计算c r 包含上下文 c 内的结果 r

一元一元函数或 Kleisli 箭头具有类型

forall m a b. Functor m => a -> m b

Kleisi 箭头是采用一个参数 a 并返回一元计算 m b 的函数。

单子是根据 Kleisli 三元组 forall m 进行规范定义的。 函子 m =>

(m, return, (=<<))

实现为类型类

class Functor m => Monad m where
   return :: t -> m t
   (=<<)  :: (a -> m b) -> m a -> m b

infixr 1 =<<

Kleisli 恒等 return 是一个 Kleisli 箭头,它将值 t 提升为 monadic上下文m扩展Kleisli应用程序 =<<应用Kleisli箭头a ->; m b 到计算 m a 的结果。

Kleisli 组合 <=< 根据扩展定义为

(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = \ x -> f =<< g x

infixr 1 <=<

<=< 组合两个 Kleisli 箭头,将左箭头应用于右箭头应用的结果。

monad 类型类的实例必须遵守monad 法则,最优雅的表述是 Kleisli 组合:forall fg h. <=<<

   f <=< return  =  f                :: c -> m d   Right identity
   return <=< g  =  g                :: b -> m c   Left identity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d   Associativity

/code > 是关联的,return 是它的左右标识。

Identity

身份类型

type Id t = t

是类型上的身份函数

Id :: * -> *

解释为函子,

   return :: t -> Id t
=      id :: t ->    t

    (=<<) :: (a -> Id b) -> Id a -> Id b
=     ($) :: (a ->    b) ->    a ->    b

    (<=<) :: (b -> Id c) -> (a -> Id b) -> (a -> Id c)
=     (.) :: (b ->    c) -> (a ->    b) -> (a ->    c)

在规范的 Haskell 中,身份 monad 被定义

newtype Id t = Id t

instance Functor Id where
   map :: (a -> b) -> Id a -> Id b
   map f (Id x) = Id (f x)

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

选项

选项类型

data Maybe t = Nothing | Just t

对计算 Maybe t 进行编码,不一定会产生结果 t< /code>,可能“失败”的计算。 选项 monad 定义为

instance Functor Maybe where
   map :: (a -> b) -> (Maybe a -> Maybe b)
   map f (Just x) = Just (f x)
   map _ Nothing  = Nothing

instance Monad Maybe where
   return :: t -> Maybe t
   return = Just

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

a -> 仅当Maybe a 产生结果时, Maybe b 才会应用于结果。

newtype Nat = Nat Int

自然数可以被编码为大于或等于零的整数。

toNat :: Int -> Maybe Nat
toNat i | i >= 0    = Just (Nat i)
        | otherwise = Nothing

自然数在减法下不封闭。

(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)

infixl 6 -?

选项 monad 涵盖了异常处理的基本形式。

(-? 20) <=< toNat :: Int -> Maybe Nat

列表

列表单子,在列表类型

data [] t = [] | t : [t]

infixr 5 :

及其加法幺半群操作“append”

(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[]       ++ ys = ys

infixr 5 ++

上编码非线性计算[t]产生自然量0, 1, .. . 结果t

instance Functor [] where
   map :: (a -> b) -> ([a] -> [b])
   map f (x : xs) = f x : map f xs
   map _ []       = []

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> [a] -> [b]
   f =<< (x : xs) = f x ++ (f =<< xs)
   _ =<< []       = []

扩展 =<< 连接 ++ 由 Kleisli 箭头的应用 f x 产生的所有列表 [b] a-> [b][a] 的元素添加到单个结果列表 [b] 中。

设正整数的真因数 n

divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]

divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)

then

forall n.  let { f = f <=< divisors } in f n   =   []

在定义 monad 类型类时,Haskell 标准使用其翻转,而不是扩展 =<<, em>绑定运算符>>=

class Applicative m => Monad m where
   (>>=) :: forall a b. m a -> (a -> m b) -> m b

   (>>) :: forall a b. m a -> m b -> m b
   m >> k = m >>= \ _ -> k
   {-# INLINE (>>) #-}

   return :: a -> m a
   return = pure

为了简单起见,这个解释使用了类型类层次结构。

class              Functor f
class Functor m => Monad m

在 Haskell 中,当前的标准层次结构是

class                  Functor f
class Functor p     => Applicative p
class Applicative m => Monad m

因为不仅每个 monad 都是函子,而且每个 applicative 都是函子,每个 monad 也是一个 applicative。

使用列表 monad,命令式伪代码

for a in (1, ..., 10)
   for b in (1, ..., 10)
      p <- a * b
      if even(p)
         yield p

粗略地转换为 do 块

do a <- [1 .. 10]
   b <- [1 .. 10]
   let p = a * b
   guard (even p)
   return p

等效的 monad 理解

[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]

并且表达式

[1 .. 10] >>= (\ a ->
   [1 .. 10] >>= (\ b ->
      let p = a * b in
         guard (even p) >>       -- [ () | even p ] >>
            return p
      )
   )

Do 表示法和 monad 理解是嵌套绑定表达式的语法糖。 绑定运算符用于单子结果的本地名称绑定。

let x = v in e    =   (\ x -> e)  $  v   =   v  &  (\ x -> e)
do { r <- m; c }  =   (\ r -> c) =<< m   =   m >>= (\ r -> c)

其中,

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

infixl 0 &

保护函数定义

guard :: Additive m => Bool -> m ()
guard True  = return ()
guard False = fail

单元类型或“空元组”

data () = ()

为支持选择失败附加单子可以使用类型类进行抽象,

class Monad m => Additive m where
   fail  :: m t
   (<|>) :: m t -> m t -> m t

infixl 3 <|>

instance Additive Maybe where
   fail = Nothing

   Nothing <|> m = m
   m       <|> _ = m

instance Additive [] where
   fail = []
   (<|>) = (++)

其中 fail<|> 形成幺半群 forall kl m.

     k <|> fail  =  k
     fail <|> l  =  l
(k <|> l) <|> m  =  k <|> (l <|> m)

fail< /code> 是加性单子的吸收/消灭零元素

_ =<< fail  =  fail

如果在

guard (even p) >> return p

even p 为真,则守卫产生 [()],并且根据 的定义code>>>,局部常量函数

\ _ -> return p

应用于结果()。 如果为 false,则守卫会生成列表 monad 的 fail ([]),这对于应用 Kleisli 箭头不会产生任何结果 >> 到,所以这个 p 被跳过。

状态

臭名昭著的是,单子用于编码状态计算。

状态处理器是一个

forall st t. st -> (t, st)

转换状态st并产生结果t的函数。 状态 st 可以是任何东西。 什么都没有,标志,计数,数组,句柄,机器,世界。

状态处理器的类型通常称为

type State st t = st -> (t, st)

状态处理器 monad 是种类 * -> * 函子状态 st。 状态处理器 monad 的 Kleisli 箭头是函数

forall st a b. a -> (State st) b

在规范的 Haskell 中,定义了状态处理器 monad 的惰性版本

newtype State st t = State { stateProc :: st -> (t, st) }

instance Functor (State st) where
   map :: (a -> b) -> ((State st) a -> (State st) b)
   map f (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  (f x, s1)

instance Monad (State st) where
   return :: t -> (State st) t
   return x = State $ \ s -> (x, s)

   (=<<) :: (a -> (State st) b) -> (State st) a -> (State st) b
   f =<< (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  stateProc (f x) s1

状态处理器通过提供初始状态来运行:

run :: State st t -> st -> (t, st)
run = stateProc

eval :: State st t -> st -> t
eval = fst . run

exec :: State st t -> st -> st
exec = snd . run

状态访问由原语 get 和 < code>put,有状态 monad 的抽象方法:

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

class Monad m => Stateful m st | m -> st where
   get :: m st
   put :: st -> m ()

m -> st 声明状态类型 st 对 monad m功能依赖; 例如,State t 将唯一确定状态类型为 t

instance Stateful (State st) st where
   get :: State st st
   get = State $ \ s -> (s, s)

   put :: st -> State st ()
   put s = State $ \ _ -> ((), s)

单元类型的使用类似于 C 中的 void

modify :: Stateful m st => (st -> st) -> m ()
modify f = do
   s <- get
   put (f s)

gets :: Stateful m st => (st -> t) -> m t
gets f = do
   s <- get
   return (f s)

gets 通常与记录字段访问器一起使用。

状态单子相当于变量线程

let s0 = 34
    s1 = (+ 1) s0
    n = (* 12) s1
    s2 = (+ 7) s1
in  (show n, s2)

,其中 s0 :: Int 是同样引用透明的,但更加优雅和实用

(flip run) 34
   (do
      modify (+ 1)
      n <- gets (* 12)
      modify (+ 7)
      return (show n)
   )

modify (+ 1) 是类型的计算State Int (),除了其效果等同于return ()

(flip run) 34
   (modify (+ 1) >>
      gets (* 12) >>= (\ n ->
         modify (+ 7) >>
            return (show n)
      )
   )

结合性单子定律可以用 >>= forall mf g.

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

do {                 do {                   do {
   r1 <- do {           x <- m;                r0 <- m;
      r0 <- m;   =      do {            =      r1 <- f r0;
      f r0                 r1 <- f x;          g r1
   };                      g r1             }
   g r1                 }
}                    }

像面向表达式的编程(例如 Rust)一样,最后一个语句一个区块代表它的产量。 绑定运算符有时称为“可编程分号”。

来自结构化命令式编程的迭代控制结构原语被模拟为单子

for :: Monad m => (a -> m b) -> [a] -> m ()
for f = foldr ((>>) . f) (return ())

while :: Monad m => m Bool -> m t -> m ()
while c m = do
   b <- c
   if b then m >> while c m
        else return ()

forever :: Monad m => m t
forever m = m >> forever m

输入/输出

data World

I/O 世界状态处理器单子是纯 Haskell 与现实世界、功能指示性和命令性操作语义的协调。 实际严格实现的近似:

type IO t = World -> (t, World)

不纯原语促进了交互

getChar         :: IO Char
putChar         :: Char -> IO ()
readFile        :: FilePath -> IO String
writeFile       :: FilePath -> String -> IO ()
hSetBuffering   :: Handle -> BufferMode -> IO ()
hTell           :: Handle -> IO Integer
. . .              . . .

使用IO原语的代码的不纯度由类型系统永久协议化。 因为纯粹性非常棒,所以 IO 中发生的事情都会保留在 IO 中。

unsafePerformIO :: IO t -> t

或者,至少,应该。

Haskell 程序的类型签名

main :: IO ()
main = putStrLn "Hello, World!"

扩展为

World -> ((), World)

改变世界的函数。

结语

对象是 Haskell 类型且态射是 Haskell 类型之间的函数的类别是“快速且松散”的类别 Hask

函子T是从类别C到类别D的映射; 对于 C 中的每个对象,在 D 中都有一个对象

Tobj :  Obj(C) -> Obj(D)
   f :: *      -> *

,对于 C 中的每个态射,在 D 中都有一个态射,

Tmor :  HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
 map :: (a -> b)   -> (f a -> f b)

其中 XYC中的对象。 HomC(X, Y) 是所有态射 X -> 的同态类 C 中的 Y。 函子必须在 D 中保留态射恒等式和复合,即 C 的“结构”。

                    Tmor    Tobj

      T(id)  =  id        : T(X) -> T(X)   Identity
T(f) . T(g)  =  T(f . g)  : T(X) -> T(Z)   Composition

Kleisli三元组给出

<T, eta, _*>

类别CKleisli范畴由endofunctor (f)的

T : C -> C

,恒等态射eta (return) 和扩展运算符 * (=<<)。

Hask 中的每个 Kleisli 态射

      f :  X -> T(Y)
      f :: a -> m b

通过扩展算子

   (_)* :  Hom(X, T(Y)) -> Hom(T(X), T(Y))
  (=<<) :: (a -> m b)   -> (m a -> m b)

被赋予 Hask 的 Kleisli 范畴中的一个态射

     f* :  T(X) -> T(Y)
(f =<<) :: m a  -> m b

Kleisli 范畴中的组合 .T 给出:外延项

 f .T g  =  f* . g       :  X -> T(Z)
f <=< g  =  (f =<<) . g  :: a -> m c

并满足范畴公理

       eta .T g  =  g                :  Y -> T(Z)   Left identity
   return <=< g  =  g                :: b -> m c

       f .T eta  =  f                :  Z -> T(U)   Right identity
   f <=< return  =  f                :: c -> m d

  (f .T g) .T h  =  f .T (g .T h)    :  X -> T(U)   Associativity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d

应用等价变换

     eta .T g  =  g
     eta* . g  =  g               By definition of .T
     eta* . g  =  id . g          forall f.  id . f  =  f
         eta*  =  id              forall f g h.  f . h  =  g . h  ==>  f  =  g

(f .T g) .T h  =  f .T (g .T h)
(f* . g)* . h  =  f* . (g* . h)   By definition of .T
(f* . g)* . h  =  f* . g* . h     . is associative
    (f* . g)*  =  f* . g*         forall f g h.  f . h  =  g . h  ==>  f  =  g

,在外延方面

               eta*  =  id                 :  T(X) -> T(X)   Left identity
       (return =<<)  =  id                 :: m t -> m t

           f* . eta  =  f                  :  Z -> T(U)      Right identity
   (f =<<) . return  =  f                  :: c -> m d

          (f* . g)*  =  f* . g*            :  T(X) -> T(Z)   Associativity
(((f =<<) . g) =<<)  =  (f =<<) . (g =<<)  :: m a -> m c

是规范给定的Monad 也可以不是用 Kleislian 外延项来定义,而是用自然变换 mu 来定义code>,在编程中称为join。 monad 根据 mu 定义为类别 C 上的三元组,由一个 endofunctor

     T :  C -> C
     f :: * -> *

和两个

   eta :  Id -> T
return :: t  -> f t

    mu :  T . T   -> T
  join :: f (f t) -> f t

满足等价的

       mu . T(mu)  =  mu . mu               :  T . T . T -> T . T   Associativity
  join . map join  =  join . join           :: f (f (f t)) -> f t

      mu . T(eta)  =  mu . eta       =  id  :  T -> T               Identity
join . map return  =  join . return  =  id  :: f t -> f t

自然变换组成。然后定义 monad 类型类

class Functor m => Monad m where
   return :: t -> m t
   join   :: m (m t) -> m t

规范的 <选项 monad 的 code>mu 实现:

instance Monad Maybe where
   return = Just

   join (Just m) = m
   join Nothing  = Nothing

concat 函数

concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat []       = []

是列表 monad 的 join

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> ([a] -> [b])
   (f =<<) = concat . map f

join 的实现可以使用等价从扩展形式翻译。

     mu  =  id*           :  T . T -> T
   join  =  (id =<<)      :: m (m t) -> m t

Philip Wadler给出了从 mu 到扩展形式的反向翻译

     f*  =  mu . T(f)     :  T(X) -> T(Y)
(f =<<)  =  join . map f  :: m a -> m b

但是为什么如此抽象的理论对编程有任何用处呢?

答案很简单:作为计算机科学家,我们重视抽象! 当我们设计软件组件的接口时,我们希望它尽可能少地透露有关实现的信息。 我们希望能够用许多替代方案、同一“概念”的许多其他“实例”来替换实现。 当我们为许多程序库设计通用接口时,更重要的是我们选择的接口具有多种实现方式。 我们如此高度重视的是单子概念的普遍性,因为范畴论是如此抽象,以至于它的概念对于编程如此有用。

那么,我们下面介绍的单子的泛化也与范畴论有着密切的联系,这一点也就不足为奇了。 但我们强调,我们的目的是非常实际的:它不是“实现范畴论”,而是找到一种更通用的方法来构造组合器库。 数学家已经为我们做了很多工作,这真是我们的幸运!

来自 John Hughes 的将单子概括为箭头

tl;dr

{-# LANGUAGE InstanceSigs #-}

newtype Id t = Id t

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

Prologue

The application operator $ of functions

forall a b. a -> b

is canonically defined

($) :: (a -> b) -> a -> b
f $ x = f x

infixr 0 $

in terms of Haskell-primitive function application f x (infixl 10).

Composition . is defined in terms of $ as

(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x

infixr 9 .

and satisfies the equivalences forall f g h.

     f . id  =  f            :: c -> d   Right identity
     id . g  =  g            :: b -> c   Left identity
(f . g) . h  =  f . (g . h)  :: a -> d   Associativity

. is associative, and id is its right and left identity.

The Kleisli triple

In programming, a monad is a functor type constructor with an instance of the monad type class. There are several equivalent variants of definition and implementation, each carrying slightly different intuitions about the monad abstraction.

A functor is a type constructor f of kind * -> * with an instance of the functor type class.

{-# LANGUAGE KindSignatures #-}

class Functor (f :: * -> *) where
   map :: (a -> b) -> (f a -> f b)

In addition to following statically enforced type protocol, instances of the functor type class must obey the algebraic functor laws forall f g.

       map id  =  id           :: f t -> f t   Identity
map f . map g  =  map (f . g)  :: f a -> f c   Composition / short cut fusion

Functor computations have the type

forall f t. Functor f => f t

A computation c r consists in results r within context c.

Unary monadic functions or Kleisli arrows have the type

forall m a b. Functor m => a -> m b

Kleisi arrows are functions that take one argument a and return a monadic computation m b.

Monads are canonically defined in terms of the Kleisli triple forall m. Functor m =>

(m, return, (=<<))

implemented as the type class

class Functor m => Monad m where
   return :: t -> m t
   (=<<)  :: (a -> m b) -> m a -> m b

infixr 1 =<<

The Kleisli identity return is a Kleisli arrow that promotes a value t into monadic context m. Extension or Kleisli application =<< applies a Kleisli arrow a -> m b to results of a computation m a.

Kleisli composition <=< is defined in terms of extension as

(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = \ x -> f =<< g x

infixr 1 <=<

<=< composes two Kleisli arrows, applying the left arrow to results of the right arrow’s application.

Instances of the monad type class must obey the monad laws, most elegantly stated in terms of Kleisli composition: forall f g h.

   f <=< return  =  f                :: c -> m d   Right identity
   return <=< g  =  g                :: b -> m c   Left identity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d   Associativity

<=< is associative, and return is its right and left identity.

Identity

The identity type

type Id t = t

is the identity function on types

Id :: * -> *

Interpreted as a functor,

   return :: t -> Id t
=      id :: t ->    t

    (=<<) :: (a -> Id b) -> Id a -> Id b
=     ($) :: (a ->    b) ->    a ->    b

    (<=<) :: (b -> Id c) -> (a -> Id b) -> (a -> Id c)
=     (.) :: (b ->    c) -> (a ->    b) -> (a ->    c)

In canonical Haskell, the identity monad is defined

newtype Id t = Id t

instance Functor Id where
   map :: (a -> b) -> Id a -> Id b
   map f (Id x) = Id (f x)

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

Option

An option type

data Maybe t = Nothing | Just t

encodes computation Maybe t that not necessarily yields a result t, computation that may “fail”. The option monad is defined

instance Functor Maybe where
   map :: (a -> b) -> (Maybe a -> Maybe b)
   map f (Just x) = Just (f x)
   map _ Nothing  = Nothing

instance Monad Maybe where
   return :: t -> Maybe t
   return = Just

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

a -> Maybe b is applied to a result only if Maybe a yields a result.

newtype Nat = Nat Int

The natural numbers can be encoded as those integers greater than or equal to zero.

toNat :: Int -> Maybe Nat
toNat i | i >= 0    = Just (Nat i)
        | otherwise = Nothing

The natural numbers are not closed under subtraction.

(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)

infixl 6 -?

The option monad covers a basic form of exception handling.

(-? 20) <=< toNat :: Int -> Maybe Nat

List

The list monad, over the list type

data [] t = [] | t : [t]

infixr 5 :

and its additive monoid operation “append”

(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[]       ++ ys = ys

infixr 5 ++

encodes nonlinear computation [t] yielding a natural amount 0, 1, ... of results t.

instance Functor [] where
   map :: (a -> b) -> ([a] -> [b])
   map f (x : xs) = f x : map f xs
   map _ []       = []

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> [a] -> [b]
   f =<< (x : xs) = f x ++ (f =<< xs)
   _ =<< []       = []

Extension =<< concatenates ++ all lists [b] resulting from applications f x of a Kleisli arrow a -> [b] to elements of [a] into a single result list [b].

Let the proper divisors of a positive integer n be

divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]

divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)

then

forall n.  let { f = f <=< divisors } in f n   =   []

In defining the monad type class, instead of extension =<<, the Haskell standard uses its flip, the bind operator >>=.

class Applicative m => Monad m where
   (>>=) :: forall a b. m a -> (a -> m b) -> m b

   (>>) :: forall a b. m a -> m b -> m b
   m >> k = m >>= \ _ -> k
   {-# INLINE (>>) #-}

   return :: a -> m a
   return = pure

For simplicity's sake, this explanation uses the type class hierarchy

class              Functor f
class Functor m => Monad m

In Haskell, the current standard hierarchy is

class                  Functor f
class Functor p     => Applicative p
class Applicative m => Monad m

because not only is every monad a functor, but every applicative is a functor and every monad is an applicative, too.

Using the list monad, the imperative pseudocode

for a in (1, ..., 10)
   for b in (1, ..., 10)
      p <- a * b
      if even(p)
         yield p

roughly translates to the do block,

do a <- [1 .. 10]
   b <- [1 .. 10]
   let p = a * b
   guard (even p)
   return p

the equivalent monad comprehension,

[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]

and the expression

[1 .. 10] >>= (\ a ->
   [1 .. 10] >>= (\ b ->
      let p = a * b in
         guard (even p) >>       -- [ () | even p ] >>
            return p
      )
   )

Do notation and monad comprehensions are syntactic sugar for nested bind expressions. The bind operator is used for local name binding of monadic results.

let x = v in e    =   (\ x -> e)  $  v   =   v  &  (\ x -> e)
do { r <- m; c }  =   (\ r -> c) =<< m   =   m >>= (\ r -> c)

where

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

infixl 0 &

The guard function is defined

guard :: Additive m => Bool -> m ()
guard True  = return ()
guard False = fail

where the unit type or “empty tuple”

data () = ()

Additive monads that support choice and failure can be abstracted over using a type class

class Monad m => Additive m where
   fail  :: m t
   (<|>) :: m t -> m t -> m t

infixl 3 <|>

instance Additive Maybe where
   fail = Nothing

   Nothing <|> m = m
   m       <|> _ = m

instance Additive [] where
   fail = []
   (<|>) = (++)

where fail and <|> form a monoid forall k l m.

     k <|> fail  =  k
     fail <|> l  =  l
(k <|> l) <|> m  =  k <|> (l <|> m)

and fail is the absorbing/annihilating zero element of additive monads

_ =<< fail  =  fail

If in

guard (even p) >> return p

even p is true, then the guard produces [()], and, by the definition of >>, the local constant function

\ _ -> return p

is applied to the result (). If false, then the guard produces the list monad’s fail ( [] ), which yields no result for a Kleisli arrow to be applied >> to, so this p is skipped over.

State

Infamously, monads are used to encode stateful computation.

A state processor is a function

forall st t. st -> (t, st)

that transitions a state st and yields a result t. The state st can be anything. Nothing, flag, count, array, handle, machine, world.

The type of state processors is usually called

type State st t = st -> (t, st)

The state processor monad is the kinded * -> * functor State st. Kleisli arrows of the state processor monad are functions

forall st a b. a -> (State st) b

In canonical Haskell, the lazy version of the state processor monad is defined

newtype State st t = State { stateProc :: st -> (t, st) }

instance Functor (State st) where
   map :: (a -> b) -> ((State st) a -> (State st) b)
   map f (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  (f x, s1)

instance Monad (State st) where
   return :: t -> (State st) t
   return x = State $ \ s -> (x, s)

   (=<<) :: (a -> (State st) b) -> (State st) a -> (State st) b
   f =<< (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  stateProc (f x) s1

A state processor is run by supplying an initial state:

run :: State st t -> st -> (t, st)
run = stateProc

eval :: State st t -> st -> t
eval = fst . run

exec :: State st t -> st -> st
exec = snd . run

State access is provided by primitives get and put, methods of abstraction over stateful monads:

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

class Monad m => Stateful m st | m -> st where
   get :: m st
   put :: st -> m ()

m -> st declares a functional dependency of the state type st on the monad m; that a State t, for example, will determine the state type to be t uniquely.

instance Stateful (State st) st where
   get :: State st st
   get = State $ \ s -> (s, s)

   put :: st -> State st ()
   put s = State $ \ _ -> ((), s)

with the unit type used analogously to void in C.

modify :: Stateful m st => (st -> st) -> m ()
modify f = do
   s <- get
   put (f s)

gets :: Stateful m st => (st -> t) -> m t
gets f = do
   s <- get
   return (f s)

gets is often used with record field accessors.

The state monad equivalent of the variable threading

let s0 = 34
    s1 = (+ 1) s0
    n = (* 12) s1
    s2 = (+ 7) s1
in  (show n, s2)

where s0 :: Int, is the equally referentially transparent, but infinitely more elegant and practical

(flip run) 34
   (do
      modify (+ 1)
      n <- gets (* 12)
      modify (+ 7)
      return (show n)
   )

modify (+ 1) is a computation of type State Int (), except for its effect equivalent to return ().

(flip run) 34
   (modify (+ 1) >>
      gets (* 12) >>= (\ n ->
         modify (+ 7) >>
            return (show n)
      )
   )

The monad law of associativity can be written in terms of >>= forall m f g.

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

or

do {                 do {                   do {
   r1 <- do {           x <- m;                r0 <- m;
      r0 <- m;   =      do {            =      r1 <- f r0;
      f r0                 r1 <- f x;          g r1
   };                      g r1             }
   g r1                 }
}                    }

Like in expression-oriented programming (e.g. Rust), the last statement of a block represents its yield. The bind operator is sometimes called a “programmable semicolon”.

Iteration control structure primitives from structured imperative programming are emulated monadically

for :: Monad m => (a -> m b) -> [a] -> m ()
for f = foldr ((>>) . f) (return ())

while :: Monad m => m Bool -> m t -> m ()
while c m = do
   b <- c
   if b then m >> while c m
        else return ()

forever :: Monad m => m t
forever m = m >> forever m

Input/Output

data World

The I/O world state processor monad is a reconciliation of pure Haskell and the real world, of functional denotative and imperative operational semantics. A close analogue of the actual strict implementation:

type IO t = World -> (t, World)

Interaction is facilitated by impure primitives

getChar         :: IO Char
putChar         :: Char -> IO ()
readFile        :: FilePath -> IO String
writeFile       :: FilePath -> String -> IO ()
hSetBuffering   :: Handle -> BufferMode -> IO ()
hTell           :: Handle -> IO Integer
. . .              . . .

The impurity of code that uses IO primitives is permanently protocolized by the type system. Because purity is awesome, what happens in IO, stays in IO.

unsafePerformIO :: IO t -> t

Or, at least, should.

The type signature of a Haskell program

main :: IO ()
main = putStrLn "Hello, World!"

expands to

World -> ((), World)

A function that transforms a world.

Epilogue

The category whiches objects are Haskell types and whiches morphisms are functions between Haskell types is, “fast and loose”, the category Hask.

A functor T is a mapping from a category C to a category D; for each object in C an object in D

Tobj :  Obj(C) -> Obj(D)
   f :: *      -> *

and for each morphism in C a morphism in D

Tmor :  HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
 map :: (a -> b)   -> (f a -> f b)

where X, Y are objects in C. HomC(X, Y) is the homomorphism class of all morphisms X -> Y in C. The functor must preserve morphism identity and composition, the “structure” of C, in D.

                    Tmor    Tobj

      T(id)  =  id        : T(X) -> T(X)   Identity
T(f) . T(g)  =  T(f . g)  : T(X) -> T(Z)   Composition

The Kleisli category of a category C is given by a Kleisli triple

<T, eta, _*>

of an endofunctor

T : C -> C

(f), an identity morphism eta (return), and an extension operator * (=<<).

Each Kleisli morphism in Hask

      f :  X -> T(Y)
      f :: a -> m b

by the extension operator

   (_)* :  Hom(X, T(Y)) -> Hom(T(X), T(Y))
  (=<<) :: (a -> m b)   -> (m a -> m b)

is given a morphism in Hask’s Kleisli category

     f* :  T(X) -> T(Y)
(f =<<) :: m a  -> m b

Composition in the Kleisli category .T is given in terms of extension

 f .T g  =  f* . g       :  X -> T(Z)
f <=< g  =  (f =<<) . g  :: a -> m c

and satisfies the category axioms

       eta .T g  =  g                :  Y -> T(Z)   Left identity
   return <=< g  =  g                :: b -> m c

       f .T eta  =  f                :  Z -> T(U)   Right identity
   f <=< return  =  f                :: c -> m d

  (f .T g) .T h  =  f .T (g .T h)    :  X -> T(U)   Associativity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d

which, applying the equivalence transformations

     eta .T g  =  g
     eta* . g  =  g               By definition of .T
     eta* . g  =  id . g          forall f.  id . f  =  f
         eta*  =  id              forall f g h.  f . h  =  g . h  ==>  f  =  g

(f .T g) .T h  =  f .T (g .T h)
(f* . g)* . h  =  f* . (g* . h)   By definition of .T
(f* . g)* . h  =  f* . g* . h     . is associative
    (f* . g)*  =  f* . g*         forall f g h.  f . h  =  g . h  ==>  f  =  g

in terms of extension are canonically given

               eta*  =  id                 :  T(X) -> T(X)   Left identity
       (return =<<)  =  id                 :: m t -> m t

           f* . eta  =  f                  :  Z -> T(U)      Right identity
   (f =<<) . return  =  f                  :: c -> m d

          (f* . g)*  =  f* . g*            :  T(X) -> T(Z)   Associativity
(((f =<<) . g) =<<)  =  (f =<<) . (g =<<)  :: m a -> m c

Monads can also be defined in terms not of Kleislian extension, but a natural transformation mu, in programming called join. A monad is defined in terms of mu as a triple over a category C, of an endofunctor

     T :  C -> C
     f :: * -> *

and two natural tranformations

   eta :  Id -> T
return :: t  -> f t

    mu :  T . T   -> T
  join :: f (f t) -> f t

satisfying the equivalences

       mu . T(mu)  =  mu . mu               :  T . T . T -> T . T   Associativity
  join . map join  =  join . join           :: f (f (f t)) -> f t

      mu . T(eta)  =  mu . eta       =  id  :  T -> T               Identity
join . map return  =  join . return  =  id  :: f t -> f t

The monad type class is then defined

class Functor m => Monad m where
   return :: t -> m t
   join   :: m (m t) -> m t

The canonical mu implementation of the option monad:

instance Monad Maybe where
   return = Just

   join (Just m) = m
   join Nothing  = Nothing

The concat function

concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat []       = []

is the join of the list monad.

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> ([a] -> [b])
   (f =<<) = concat . map f

Implementations of join can be translated from extension form using the equivalence

     mu  =  id*           :  T . T -> T
   join  =  (id =<<)      :: m (m t) -> m t

The reverse translation from mu to extension form is given by

     f*  =  mu . T(f)     :  T(X) -> T(Y)
(f =<<)  =  join . map f  :: m a -> m b

But why should a theory so abstract be of any use for programming?

The answer is simple: as computer scientists, we value abstraction! When we design the interface to a software component, we want it to reveal as little as possible about the implementation. We want to be able to replace the implementation with many alternatives, many other ‘instances’ of the same ‘concept’. When we design a generic interface to many program libraries, it is even more important that the interface we choose have a variety of implementations. It is the generality of the monad concept which we value so highly, it is because category theory is so abstract that its concepts are so useful for programming.

It is hardly suprising, then, that the generalisation of monads that we present below also has a close connection to category theory. But we stress that our purpose is very practical: it is not to ‘implement category theory’, it is to find a more general way to structure combinator libraries. It is simply our good fortune that mathematicians have already done much of the work for us!

from Generalising Monads to Arrows by John Hughes

春花秋月 2024-07-11 01:01:51

解释“什么是单子”有点像说“什么是数字?” 我们一直使用数字。 但想象一下你遇到了一个对数字一无所知的人。 到底你会如何解释数字是什么? 您如何开始描述为什么这可能有用?

什么是单子? 简短的回答:这是一种将操作链接在一起的特定方式。

本质上,您正在编写执行步骤并将它们与“绑定函数”链接在一起。 (在 Haskell 中,它的名称为 >>=。)您可以自己编写对绑定运算符的调用,也可以使用语法糖,使编译器为您插入这些函数调用。 但无论哪种方式,每个步骤都是通过调用此绑定函数来分隔的。

所以bind函数就像一个分号; 它分隔了流程中的步骤。 绑定函数的工作是获取上一步的输出,并将其输入下一步。

这听起来不太难,对吧? 但单子有不止一种。 为什么? 如何?

那么,绑定函数可以只获取一个步骤的结果,并将其提供给下一步。 但如果这就是 monad 所做的“全部”……那实际上并不是很有用。 理解这一点很重要:每个有用的单子除了作为一个单子之外还可以做一些其他的事情。 每个有用的单子都有一种“特殊的力量”,这使得它独一无二。

(一个不做任何事情的单子被称为“身份单子”。就像身份函数一样,这听起来完全是毫无意义的事情,但事实证明并非如此......但那是另一个故事了™ .)

基本上,每个 monad 都有自己的绑定函数实现。 您可以编写一个绑定函数,以便它在执行步骤之间执行一些操作。 例如:

  • 如果每个步骤都返回成功/失败指示符,则仅当前一步成功时,您才可以让 Bind 执行下一步。 这样,失败的步骤会“自动”中止整个序列,而无需您进行任何条件测试。 (失败 Monad。)

  • 扩展这个想法,您可以实现“异常”。 (Error MonadException Monad。)因为它们是您自己定义的而不是语言功能,所以您可以定义它们的工作方式。 (例如,也许您想忽略前两个异常,并且仅在抛出第三个​​异常时中止。)

  • 您可以使每个步骤返回多个结果,并且让绑定函数对它们进行循环,将每个函数送入下一步。 这样,在处理多个结果时,您就不必到处编写循环。 绑定功能“自动”为您完成所有这些工作。 (List Monad。)

  • 除了将“结果”从一个步骤传递到另一个步骤之外,您还可以让绑定函数传递额外的数据。 这些数据现在不会显示在您的源代码中,但您仍然可以从任何地方访问它,而无需手动将其传递给每个函数。 (Reader Monad。)

  • 您可以这样做,以便替换“额外数据”。 这允许您模拟破坏性更新,而无需实际执行破坏性更新。 (State Monad 及其近亲 Writer Monad。)

  • 因为您只是模拟破坏性更新,所以您可以简单地做一些事情对于真正的破坏性更新来说这是不可能的。 例如,您可以撤消上次更新,或恢复到旧版本

  • 创建一个可以暂停计算的 monad,这样您就可以暂停程序,修改内部状态数据,然后恢复它。

  • 您可以将“延续”实现为 monad。 这使您能够打破人们的想法!

所有这些以及更多的事情都可以通过 monad 实现。 当然,所有这一切在没有单子的情况下也是完全可能的。 使用 monad 会更容易

Explaining "what is a monad" is a bit like saying "what is a number?" We use numbers all the time. But imagine you met someone who didn't know anything about numbers. How the heck would you explain what numbers are? And how would you even begin to describe why that might be useful?

What is a monad? The short answer: It's a specific way of chaining operations together.

In essence, you're writing execution steps and linking them together with the "bind function". (In Haskell, it's named >>=.) You can write the calls to the bind operator yourself, or you can use syntax sugar which makes the compiler insert those function calls for you. But either way, each step is separated by a call to this bind function.

So the bind function is like a semicolon; it separates the steps in a process. The bind function's job is to take the output from the previous step, and feed it into the next step.

That doesn't sound too hard, right? But there is more than one kind of monad. Why? How?

Well, the bind function can just take the result from one step, and feed it to the next step. But if that's "all" the monad does... that actually isn't very useful. And that's important to understand: Every useful monad does something else in addition to just being a monad. Every useful monad has a "special power", which makes it unique.

(A monad that does nothing special is called the "identity monad". Rather like the identity function, this sounds like an utterly pointless thing, yet turns out not to be... But that's another story™.)

Basically, each monad has its own implementation of the bind function. And you can write a bind function such that it does hoopy things between execution steps. For example:

  • If each step returns a success/failure indicator, you can have bind execute the next step only if the previous one succeeded. In this way, a failing step aborts the whole sequence "automatically", without any conditional testing from you. (The Failure Monad.)

  • Extending this idea, you can implement "exceptions". (The Error Monad or Exception Monad.) Because you're defining them yourself rather than it being a language feature, you can define how they work. (E.g., maybe you want to ignore the first two exceptions and only abort when a third exception is thrown.)

  • You can make each step return multiple results, and have the bind function loop over them, feeding each one into the next step for you. In this way, you don't have to keep writing loops all over the place when dealing with multiple results. The bind function "automatically" does all that for you. (The List Monad.)

  • As well as passing a "result" from one step to another, you can have the bind function pass extra data around as well. This data now doesn't show up in your source code, but you can still access it from anywhere, without having to manually pass it to every function. (The Reader Monad.)

  • You can make it so that the "extra data" can be replaced. This allows you to simulate destructive updates, without actually doing destructive updates. (The State Monad and its cousin the Writer Monad.)

  • Because you're only simulating destructive updates, you can trivially do things that would be impossible with real destructive updates. For example, you can undo the last update, or revert to an older version.

  • You can make a monad where calculations can be paused, so you can pause your program, go in and tinker with internal state data, and then resume it.

  • You can implement "continuations" as a monad. This allows you to break people's minds!

All of this and more is possible with monads. Of course, all of this is also perfectly possible without monads too. It's just drastically easier using monads.

来世叙缘 2024-07-11 01:01:51

第一:如果你不是数学家,单子这个术语有点空洞。 另一个术语是计算生成器,它更能描述它们的实际用途。

它们是链接操作的模式。 它看起来有点像面向对象语言中的方法链,但机制略有不同。

该模式主要用于函数式语言(尤其是普遍使用 monad 的 Haskell),但也可以用于任何支持高阶函数(即可以将其他函数作为参数的函数)的语言。

JavaScript 中的数组支持该模式,因此让我们使用它作为第一个示例。

该模式的要点是我们有一个类型(在本例中为Array),它有一个以函数作为参数的方法。 提供的操作必须返回相同类型的实例(即返回Array)。

首先是一个使用 monad 模式的方法链接示例:

[1,2,3].map(x => x + 1)

结果是[2,3,4]。 该代码不符合 monad 模式,因为我们作为参数提供的函数返回一个数字,而不是一个数组。 monad 形式的相同逻辑是:

[1,2,3].flatMap(x => [x + 1])

这里我们提供一个返回数组的操作,所以现在它符合该模式。 flatMap 方法为数组中的每个元素执行提供的函数。 它期望每次调用的结果都是一个数组(而不是单个值),但会将结果数组集合并到单个数组中。 所以最终结果是相同的,数组[2,3,4]

(提供给 mapflatMap 等方法的函数参数在 JavaScript 中通常称为“回调”。我将其称为“操作”,因为它更通用。 )

如果我们链接多个操作(以传统方式):

[1,2,3].map(a => a + 1).filter(b => b != 3)

得到数组 [2,4]

以 monad 形式进行相同的链接:

[1,2,3].flatMap(a => [a + 1]).flatMap(b => b != 3 ? [b] : [])

产生相同的结果,数组 [2,4 ]

你会立即注意到 monad 形式比非 monad 难看得多! 这只是表明单子不一定是“好的”。 它们是一种有时有利有时不利的模式。

请注意,单子模式可以以不同的方式组合:

[1,2,3].flatMap(a => [a + 1].flatMap(b => b != 3 ? [b] : []))

这里的绑定是嵌套的而不是链接的,但结果是相同的。 这是 monad 的一个重要属性,我们稍后会看到。 这意味着两个操作的组合可以被视为单个操作。

该操作允许返回具有不同元素类型的数组,例如将数字数组转换为字符串数组或其他数组; 只要它仍然是一个数组。

可以使用 Typescript 表示法更正式地描述这一点。 数组的类型为 Array,其中 T 是数组中元素的类型。 flatMap() 方法采用 T => 类型的函数参数。 Array 并返回 Array

一般来说,一个 monad 是任何类型 Foo ,它有一个“bind”方法,该方法接受一个 Bar => 类型的函数参数。 Foo 并返回 Foo

这回答了什么单子。 这个答案的其余部分将尝试通过示例解释为什么 monad 在像 Haskell 这样的语言中是一种有用的模式,因为它对它们有很好的支持。

Haskell 和 Do-notation

要将映射/过滤器示例直接转换为 Haskell,我们将 flatMap 替换为 >>= 运算符

[1,2,3] >>= \a -> [a+1] >>= \b -> if b == 3 then [] else [b] 

>>= 运算符是 Haskell 中的绑定函数。 当操作数是列表时,它与 JavaScript 中的 flatMap 的作用相同,但对于其他类型,它具有不同含义的重载。

但是 Haskell 也有一个专用于 monad 表达式的语法,即 do 块,它完全隐藏了绑定运算符:

do 
  a <- [1,2,3] 
  b <- [a+1] 
  if b == 3 then [] else [b] 

这隐藏了“管道”,让您专注于每一步应用的实际操作。

在 do 块中,每一行都是一个操作。 该约束仍然要求块中的所有操作必须返回相同的类型。 由于第一个表达式是一个列表,因此其他操作也必须返回一个列表。

后向箭头 <- 看起来像是一个赋值,但请注意,这是在绑定中传递的参数。 因此,当右侧的表达式是整数列表时,左侧的变量将是单个整数 - 但将为列表中的每个整数执行。

示例:安全导航(Maybe 类型)

关于列表的内容已经说得够多了,让我们看看 monad 模式如何对其他类型有用。

某些函数可能并不总是返回有效值。 在 Haskell 中,这由 Maybe 类型表示,它是一个可以是 Just valueNothing 的选项。

始终返回有效值的链接操作当然很简单:

streetName = getStreetName (getAddress (getUser 17)) 

但是如果任何函数可以返回 Nothing 该怎么办? 我们需要单独检查每个结果,如果不是 Nothing,则仅将值传递给下一个函数:

case getUser 17 of
      Nothing -> Nothing 
      Just user ->
         case getAddress user of
            Nothing -> Nothing 
            Just address ->
              getStreetName address

相当多的重复检查! 想象一下,如果链条更长。 Haskell 使用 Maybe 的 monad 模式解决了这个问题:

do
  user <- getUser 17
  addr <- getAddress user
  getStreetName addr

这个 do 块调用了 Maybe 类型的绑定函数(因为第一个表达式是Maybe)。 如果值为 Just value,绑定函数仅执行以下操作,否则它仅传递 Nothing

这里使用 monad 模式来避免重复的代码。 这类似于其他一些语言使用宏来简化语法的方式,尽管宏以非常不同的方式实现相同的目标。

请注意,Haskell 中 monad 模式和 monad 友好语法的组合导致了更清晰的代码。 在像 JavaScript 这样没有任何特殊语法支持 monad 的语言中,我怀疑 monad 模式是否能够简化这种情况下的代码。

可变状态

Haskell 不支持可变状态。 所有变量都是常量,所有值都是不可变的。 但是 State 类型可用于模拟可变状态编程:

add2 :: State Integer Integer
add2 = do
        -- add 1 to state
         x <- get
         put (x + 1)
         -- increment in another way
         modify (+1)
         -- return state
         get


evalState add2 7
=> 9

add2 函数构建一个 monad 链,然后使用 7 作为初始状态对其进行评估。

显然这只有在 Haskell 中才有意义。 其他语言支持开箱即用的可变状态。 Haskell 通常是“选择加入”语言功能的——您可以在需要时启用可变状态,并且类型系统确保效果是明确的。 IO 是另一个例子。

IO

IO 类型用于链接和执行“不纯”函数。

与任何其他实用语言一样,Haskell 有许多与外界交互的内置函数:putStrLinereadLine 等。 这些函数被称为“不纯的”,因为它们要么引起副作用,要么产生不确定的结果。 即使像获取时间这样简单的事情也被认为是不纯粹的,因为结果是不确定的——使用相同的参数调用它两次可能会返回不同的值。

纯函数是确定性的——它的结果完全取决于传递的参数,除了返回值之外,它对环境没有副作用。

Haskell 大力鼓励使用纯函数——这是该语言的一个主要卖点。 不幸的是,对于纯粹主义者来说,您需要一些不纯粹的函数来完成任何有用的事情。 Haskell 的妥协是彻底分离纯函数和非纯函数,并保证纯函数无法直接或间接执行非纯函数。

这是通过为所有不纯函数指定 IO 类型来保证的。 Haskell程序的入口点是main函数,它具有IO类型,因此我们可以在顶层执行不纯的函数。

但是语言如何阻止纯函数执行不纯函数呢? 这是由于 Haskell 的惰性。 仅当某个函数的输出被其他函数消耗时,该函数才会被执行。 但是除了将 IO 值分配给 main 之外,无法使用它。 因此,如果一个函数想要执行一个不纯的函数,它必须连接到 main 并具有 IO 类型。

对 IO 操作使用 monad 链还可以确保它们以线性且可预测的顺序执行,就像命令式语言中的语句一样。

这给我们带来了大多数人会在 Haskell 中编写的第一个程序:

main :: IO ()
main = do 
        putStrLn ”Hello World”

当只有一个操作因此没有任何可绑定时,do 关键字是多余的,但为了一致性我还是保留它。

() 类型表示“void”。 这种特殊的返回类型仅对因副作用而调用的 IO 函数有用。

一个更长的示例:

main = do
    putStrLn "What is your name?"
    name <- getLine
    putStrLn ("hello" ++ name)

这构建了一系列 IO 操作,并且由于它们被分配给 main 函数,因此它们被执行。

IOMaybe 进行比较显示了 monad 模式的多功能性。 对于Maybe,该模式用于通过将条件逻辑移至绑定函数来避免重复代码。 对于IO,该模式用于确保IO类型的所有操作都是有序的,并且IO操作不能“泄漏”到纯函数。

总结

在我的主观意见中,单子模式只有在对该模式有一些内置支持的语言中才真正有价值。 否则只会导致代码过于复杂。 但是 Haskell(和其他一些语言)有一些内置的支持,隐藏了繁琐的部分,然后该模式可以用于各种有用的事情。 例如:

  • 避免重复代码(也许
  • 添加语言功能,例如可变状态或程序分隔区域的异常。
  • 将不好的东西与好东西隔离开来 (IO)
  • 嵌入式领域特定语言 (Parser)
  • 将 GOTO 添加到语言中。

First: The term monad is a bit vacuous if you are not a mathematician. An alternative term is computation builder which is a bit more descriptive of what they are actually useful for.

They are a pattern for chaining operations. It looks a bit like method chaining in object-oriented languages, but the mechanism is slightly different.

The pattern is mostly used in functional languages (especially Haskell which uses monads pervasively) but can be used in any language which support higher-order functions (that is, functions which can take other functions as arguments).

Arrays in JavaScript support the pattern, so let’s use that as the first example.

The gist of the pattern is we have a type (Array in this case) which has a method which takes a function as argument. The operation supplied must return an instance of the same type (i.e. return an Array).

First an example of method chaining which does not use the monad pattern:

[1,2,3].map(x => x + 1)

The result is [2,3,4]. The code does not conform to the monad pattern, since the function we are supplying as an argument returns a number, not an Array. The same logic in monad form would be:

[1,2,3].flatMap(x => [x + 1])

Here we supply an operation which returns an Array, so now it conforms to the pattern. The flatMap method executes the provided function for every element in the array. It expects an array as result for each invocation (rather than single values), but merges the resulting set of arrays into a single array. So the end result is the same, the array [2,3,4].

(The function argument provided to a method like map or flatMap is often called a "callback" in JavaScript. I will call it the "operation" since it is more general.)

If we chain multiple operations (in the traditional way):

[1,2,3].map(a => a + 1).filter(b => b != 3)

Results in the array [2,4]

The same chaining in monad form:

[1,2,3].flatMap(a => [a + 1]).flatMap(b => b != 3 ? [b] : [])

Yields the same result, the array [2,4].

You will immediately notice that the monad form is quite a bit uglier than the non-monad! This just goes to show that monads are not necessarily “good”. They are a pattern which is sometimes beneficial and sometimes not.

Do note that the monad pattern can be combined in a different way:

[1,2,3].flatMap(a => [a + 1].flatMap(b => b != 3 ? [b] : []))

Here the binding is nested rather than chained, but the result is the same. This is an important property of monads as we will see later. It means two operations combined can be treated the same as a single operation.

The operation is allowed to return an array with different element types, for example transforming an array of numbers into an array of strings or something else; as long as it still an Array.

This can be described a bit more formally using Typescript notation. An array has the type Array<T>, where T is the type of the elements in the array. The method flatMap() takes a function argument of the type T => Array<U> and returns an Array<U>.

Generalized, a monad is any type Foo<Bar> which has a "bind" method which takes a function argument of type Bar => Foo<Baz> and returns a Foo<Baz>.

This answers what monads are. The rest of this answer will try to explain through examples why monads can be a useful pattern in a language like Haskell which has good support for them.

Haskell and Do-notation

To translate the map/filter example directly to Haskell, we replace flatMap with the >>= operator:

[1,2,3] >>= \a -> [a+1] >>= \b -> if b == 3 then [] else [b] 

The >>= operator is the bind function in Haskell. It does the same as flatMap in JavaScript when the operand is a list, but it is overloaded with different meaning for other types.

But Haskell also has a dedicated syntax for monad expressions, the do-block, which hides the bind operator altogether:

do 
  a <- [1,2,3] 
  b <- [a+1] 
  if b == 3 then [] else [b] 

This hides the "plumbing" and lets you focus on the actual operations applied at each step.

In a do-block, each line is an operation. The constraint still holds that all operations in the block must return the same type. Since the first expression is a list, the other operations must also return a list.

The back-arrow <- looks deceptively like an assignment, but note that this is the parameter passed in the bind. So, when the expression on the right side is a List of Integers, the variable on the left side will be a single Integer – but will be executed for each integer in the list.

Example: Safe navigation (the Maybe type)

Enough about lists, lets see how the monad pattern can be useful for other types.

Some functions may not always return a valid value. In Haskell this is represented by the Maybe-type, which is an option that is either Just value or Nothing.

Chaining operations which always return a valid value is of course straightforward:

streetName = getStreetName (getAddress (getUser 17)) 

But what if any of the functions could return Nothing? We need to check each result individually and only pass the value to the next function if it is not Nothing:

case getUser 17 of
      Nothing -> Nothing 
      Just user ->
         case getAddress user of
            Nothing -> Nothing 
            Just address ->
              getStreetName address

Quite a lot of repetitive checks! Imagine if the chain was longer. Haskell solves this with the monad pattern for Maybe:

do
  user <- getUser 17
  addr <- getAddress user
  getStreetName addr

This do-block invokes the bind-function for the Maybe type (since the result of the first expression is a Maybe). The bind-function only executes the following operation if the value is Just value, otherwise it just passes the Nothing along.

Here the monad-pattern is used to avoid repetitive code. This is similar to how some other languages use macros to simplify syntax, although macros achieve the same goal in a very different way.

Note that it is the combination of the monad pattern and the monad-friendly syntax in Haskell which result in the cleaner code. In a language like JavaScript without any special syntax support for monads, I doubt the monad pattern would be able to simplify the code in this case.

Mutable state

Haskell does not support mutable state. All variables are constants and all values immutable. But the State type can be used to emulate programming with mutable state:

add2 :: State Integer Integer
add2 = do
        -- add 1 to state
         x <- get
         put (x + 1)
         -- increment in another way
         modify (+1)
         -- return state
         get


evalState add2 7
=> 9

The add2 function builds a monad chain which is then evaluated with 7 as the initial state.

Obviously this is something which only makes sense in Haskell. Other languages support mutable state out of the box. Haskell is generally "opt-in" on language features - you enable mutable state when you need it, and the type system ensures the effect is explicit. IO is another example of this.

IO

The IO type is used for chaining and executing “impure” functions.

Like any other practical language, Haskell has a bunch of built-in functions which interface with the outside world: putStrLine, readLine and so on. These functions are called “impure” because they either cause side effects or have non-deterministic results. Even something simple like getting the time is considered impure because the result is non-deterministic – calling it twice with the same arguments may return different values.

A pure function is deterministic – its result depends purely on the arguments passed and it has no side effects on the environment beside returning a value.

Haskell heavily encourages the use of pure functions – this is a major selling point of the language. Unfortunately for purists, you need some impure functions to do anything useful. The Haskell compromise is to cleanly separate pure and impure, and guarantee that there is no way that pure functions can execute impure functions, directly or indirect.

This is guaranteed by giving all impure functions the IO type. The entry point in Haskell program is the main function which have the IO type, so we can execute impure functions at the top level.

But how does the language prevent pure functions from executing impure functions? This is due to the lazy nature of Haskell. A function is only executed if its output is consumed by some other function. But there is no way to consume an IO value except to assign it to main. So if a function wants to execute an impure function, it has to be connected to main and have the IO type.

Using monad chaining for IO operations also ensures that they are executed in a linear and predictable order, just like statements in an imperative language.

This brings us to the first program most people will write in Haskell:

main :: IO ()
main = do 
        putStrLn ”Hello World”

The do keyword is superfluous when there is only a single operation and therefore nothing to bind, but I keep it anyway for consistency.

The () type means “void”. This special return type is only useful for IO functions called for their side effect.

A longer example:

main = do
    putStrLn "What is your name?"
    name <- getLine
    putStrLn ("hello" ++ name)

This builds a chain of IO operations, and since they are assigned to the main function, they get executed.

Comparing IO with Maybe shows the versatility of the monad pattern. For Maybe, the pattern is used to avoid repetitive code by moving conditional logic to the binding function. For IO, the pattern is used to ensure that all operations of the IO type are sequenced and that IO operations cannot "leak" to pure functions.

Summing up

In my subjective opinion, the monad pattern is only really worthwhile in a language which has some built-in support for the pattern. Otherwise it just leads to overly convoluted code. But Haskell (and some other languages) have some built-in support which hides the tedious parts, and then the pattern can be used for a variety of useful things. Like:

  • Avoiding repetitive code (Maybe)
  • Adding language features like mutable state or exceptions for delimited areas of the program.
  • Isolating icky stuff from nice stuff (IO)
  • Embedded domain-specific languages (Parser)
  • Adding GOTO to the language.
酒与心事 2024-07-11 01:01:51

实际上,与人们对 Monad 的普遍理解相反,它们与状态无关。 Monad 只是一种包装事物的方法,并提供对包装的事物进行操作而无需解开它的方法。

例如,您可以创建一个类型来包装另一个类型,在 Haskell 中:

data Wrapped a = Wrap a

包装我们定义的内容

return :: a -> Wrapped a
return x = Wrap x

执行操作而不展开包装,假设您有一个函数 f :: a -> b,那么您可以执行此操作来提升该函数以对包装的值进行操作:

fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)

这就是需要理解的全部内容。 然而,事实证明,有一个更通用的函数可以做到这一点提升,那就是bind

bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x

bind可以做的比< code>fmap,但反之则不然。 实际上,fmap只能用bindreturn来定义。 因此,在定义一个 monad 时......你给出它的类型(这里是Wrapped a),然后说明它的returnbind操作是如何工作的。

很酷的是,事实证明这是一种普遍的模式,它随处可见,以纯粹的方式封装状态只是其中之一。

有关如何使用 monad 引入函数依赖关系并从而控制求值顺序(例如在 Haskell 的 IO monad 中使用)的好文章,请查看 IO 内部

至于理解monad,不用太担心。 阅读您感兴趣的内容,如果您不能立即理解,请不要担心。 那么,只需深入研究像 Haskell 这样的语言就是正确的选择。 单子是通过练习慢慢进入你的大脑的事物之一,有一天你突然意识到你理解了它们。

Actually, contrary to common understanding of Monads, they have nothing to do with state. Monads are simply a way to wrapping things and provide methods to do operations on the wrapped stuff without unwrapping it.

For example, you can create a type to wrap another one, in Haskell:

data Wrapped a = Wrap a

To wrap stuff we define

return :: a -> Wrapped a
return x = Wrap x

To perform operations without unwrapping, say you have a function f :: a -> b, then you can do this to lift that function to act on wrapped values:

fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)

That's about all there is to understand. However, it turns out that there is a more general function to do this lifting, which is bind:

bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x

bind can do a bit more than fmap, but not vice versa. Actually, fmap can be defined only in terms of bind and return. So, when defining a monad.. you give its type (here it was Wrapped a) and then say how its return and bind operations work.

The cool thing is that this turns out to be such a general pattern that it pops up all over the place, encapsulating state in a pure way is only one of them.

For a good article on how monads can be used to introduce functional dependencies and thus control order of evaluation, like it is used in Haskell's IO monad, check out IO Inside.

As for understanding monads, don't worry too much about it. Read about them what you find interesting and don't worry if you don't understand right away. Then just diving in a language like Haskell is the way to go. Monads are one of these things where understanding trickles into your brain by practice, one day you just suddenly realize you understand them.

原谅过去的我 2024-07-11 01:01:51

但是,你本可以发明 Monad!

sigfpe 说:

<块引用>

但是所有这些都将 monad 视为需要解释的深奥事物。 但我想说的是,它们根本就不深奥。 事实上,面对函数式编程中的各种问题,您将不可避免地找到某些解决方案,所有这些都是 monad 的示例。 事实上,如果你还没有发明它们,我希望你现在就能发明它们。 然后,您会注意到所有这些解决方案实际上都是经过伪装的相同解决方案。 读完本文后,您可能能够更好地理解有关 monad 的其他文档,因为您会认识到您所看到的一切都是您已经发明的东西。

单子试图解决的许多问题都与副作用问题有关。 所以我们将从他们开始。 (请注意,单子让您做的不仅仅是处理副作用,特别是许多类型的容器对象都可以被视为单子。一些对单子的介绍发现很难调和单子的这两种不同用途,并且只关注其中一种或另一个。)

在命令式编程语言(例如 C++)中,函数的行为与数学函数完全不同。 例如,假设我们有一个 C++ 函数,它接受单个浮点参数并返回浮点结果。 从表面上看,它可能有点像将实数映射到实数的数学函数,但 C++ 函数可以做的不仅仅是返回一个取决于其参数的数字。 它可以读取和写入全局变量的值,以及将输出写入屏幕并接收用户的输入。 然而,在纯函数式语言中,函数只能读取其参数中提供给它的内容,并且它对世界产生影响的唯一方式是通过它返回的值。

But, You could have invented Monads!

sigfpe says:

But all of these introduce monads as something esoteric in need of explanation. But what I want to argue is that they aren't esoteric at all. In fact, faced with various problems in functional programming you would have been led, inexorably, to certain solutions, all of which are examples of monads. In fact, I hope to get you to invent them now if you haven't already. It's then a small step to notice that all of these solutions are in fact the same solution in disguise. And after reading this, you might be in a better position to understand other documents on monads because you'll recognise everything you see as something you've already invented.

Many of the problems that monads try to solve are related to the issue of side effects. So we'll start with them. (Note that monads let you do more than handle side-effects, in particular many types of container object can be viewed as monads. Some of the introductions to monads find it hard to reconcile these two different uses of monads and concentrate on just one or the other.)

In an imperative programming language such as C++, functions behave nothing like the functions of mathematics. For example, suppose we have a C++ function that takes a single floating point argument and returns a floating point result. Superficially it might seem a little like a mathematical function mapping reals to reals, but a C++ function can do more than just return a number that depends on its arguments. It can read and write the values of global variables as well as writing output to the screen and receiving input from the user. In a pure functional language, however, a function can only read what is supplied to it in its arguments and the only way it can have an effect on the world is through the values it returns.

锦爱 2024-07-11 01:01:51

monad 是一种具有两种操作的数据类型:>>=(又名 bind)和 return(又名 unit)代码>)。 return 接受任意值并用它创建 monad 的实例。 >>= 获取 monad 的一个实例并在其上映射一个函数。 (您已经可以看到 monad 是一种奇怪的数据类型,因为在大多数编程语言中,您无法编写接受任意值并从中创建类型的函数。Monad 使用一种 参数多态性。)

在 Haskell 表示法中,编写了 monad 接口。

class Monad m where
  return :: a -> m a
  (>>=) :: forall a b . m a -> (a -> m b) -> m b

这些操作应该遵循某些“法律”,但这并不是非常重要:“法律”只是编纂了操作的合理实现应该表现的方式(基本上,>>=return 应该就值如何转换为 monad 实例达成一致,并且 >>= 是关联的)。

Monad 不仅仅是状态和 I/O:它们抽象了一种常见的计算模式,包括处理状态、I/O、异常和非确定性。 最容易理解的 monad 可能是列表和选项类型:

instance Monad [ ] where
    []     >>= k = []
    (x:xs) >>= k = k x ++ (xs >>= k)
    return x     = [x]

instance Monad Maybe where
    Just x  >>= k = k x
    Nothing >>= k = Nothing
    return x      = Just x

其中 []: 是列表构造函数,++ 是连接运算符, JustNothingMaybe 构造函数。 这两个 monad 都封装了各自数据类型上常见且有用的计算模式(请注意,两者都与副作用或 I/O 无关)。

你真的必须尝试编写一些不平凡的 Haskell 代码来理解 monad 的含义以及它们为什么有用。

A monad is a datatype that has two operations: >>= (aka bind) and return (aka unit). return takes an arbitrary value and creates an instance of the monad with it. >>= takes an instance of the monad and maps a function over it. (You can see already that a monad is a strange kind of datatype, since in most programming languages you couldn't write a function that takes an arbitrary value and creates a type from it. Monads use a kind of parametric polymorphism.)

In Haskell notation, the monad interface is written

class Monad m where
  return :: a -> m a
  (>>=) :: forall a b . m a -> (a -> m b) -> m b

These operations are supposed to obey certain "laws", but that's not terrifically important: the "laws" just codify the way sensible implementations of the operations ought to behave (basically, that >>= and return ought to agree about how values get transformed into monad instances and that >>= is associative).

Monads are not just about state and I/O: they abstract a common pattern of computation that includes working with state, I/O, exceptions, and non-determinism. Probably the simplest monads to understand are lists and option types:

instance Monad [ ] where
    []     >>= k = []
    (x:xs) >>= k = k x ++ (xs >>= k)
    return x     = [x]

instance Monad Maybe where
    Just x  >>= k = k x
    Nothing >>= k = Nothing
    return x      = Just x

where [] and : are the list constructors, ++ is the concatenation operator, and Just and Nothing are the Maybe constructors. Both of these monads encapsulate common and useful patterns of computation on their respective data types (note that neither has anything to do with side effects or I/O).

You really have to play around writing some non-trivial Haskell code to appreciate what monads are about and why they are useful.

苦妄 2024-07-11 01:01:51

您应该首先了解函子是什么。 在此之前,请了解高阶函数。

高阶函数只是一个以函数作为参数的函数。

函子是任何类型构造T,其中存在一个高阶函数,称为map,它转换类型的函数>一-> b (给定任意两种类型 ab)到函数 T a ->; T b 。 此 map 函数还必须遵守恒等和组合法则,以便以下表达式对于所有 pq 返回 true(Haskell 表示法):

map id = id
map (p . q) = map p . map q

例如,如果一个名为 List 的类型构造函数配备了 (a -> b) ->; 类型的函数,那么它就是一个函子。 列出一个-> 列表 b 遵守上述规则。 唯一实际的实现是显而易见的。 生成的 List a -> List b 函数迭代给定列表,为每个元素调用 (a -> b) 函数,并返回结果列表。

monad 本质上只是一个函子 T,具有两个额外的方法,join,类型为 T( Ta)-> T aunit(有时称为 returnforkpure),类型为 <代码>a-> 塔。 对于 Haskell 中的列表:

join :: [[a]] -> [a]
pure :: a -> [a]

为什么有用? 例如,因为您可以使用返回列表的函数来映射列表。 Join 获取结果列表并将它们连接起来。 List 是一个 monad,因为这是可能的。

您可以编写一个函数来执行map,然后join。 此函数称为 bindflatMap(>>=)(=<<) 。 这通常是 Haskell 中给出 monad 实例的方式。

一个 monad 必须满足某些定律,即 join 必须是关联的。 这意味着,如果您有一个 [[[a]]] 类型的值 x,则 join (join x) 应等于 连接(映射连接x)。 并且 pure 必须是 join 的标识,使得 join (pure x) == x

You should first understand what a functor is. Before that, understand higher-order functions.

A higher-order function is simply a function that takes a function as an argument.

A functor is any type construction T for which there exists a higher-order function, call it map, that transforms a function of type a -> b (given any two types a and b) into a function T a -> T b. This map function must also obey the laws of identity and composition such that the following expressions return true for all p and q (Haskell notation):

map id = id
map (p . q) = map p . map q

For example, a type constructor called List is a functor if it comes equipped with a function of type (a -> b) -> List a -> List b which obeys the laws above. The only practical implementation is obvious. The resulting List a -> List b function iterates over the given list, calling the (a -> b) function for each element, and returns the list of the results.

A monad is essentially just a functor T with two extra methods, join, of type T (T a) -> T a, and unit (sometimes called return, fork, or pure) of type a -> T a. For lists in Haskell:

join :: [[a]] -> [a]
pure :: a -> [a]

Why is that useful? Because you could, for example, map over a list with a function that returns a list. Join takes the resulting list of lists and concatenates them. List is a monad because this is possible.

You can write a function that does map, then join. This function is called bind, or flatMap, or (>>=), or (=<<). This is normally how a monad instance is given in Haskell.

A monad has to satisfy certain laws, namely that join must be associative. This means that if you have a value x of type [[[a]]] then join (join x) should equal join (map join x). And pure must be an identity for join such that join (pure x) == x.

泼猴你往哪里跑 2024-07-11 01:01:51

[免责声明:我仍在尝试完全理解 monads。 以下只是我目前所了解的。 如果错了,希望有识之士能在地毯上给我打电话。]

Arnar 写道:

Monad 只是一种包装事物的方法,并提供对包装的事物进行操作而无需解开它的方法。

正是如此。 这个想法是这样的:

  1. 你获取某种值并用一些附加信息包装它。 就像值是某种类型的一样(例如整数或字符串),附加信息也是某种类型的。

    例如,该额外信息可能是 MaybeIO

  2. 然后您将拥有一些运算符,允许您在携带附加信息的同时对包装的数据进行操作。 这些运算符使用附加信息来决定如何更改对包装值的操作行为。

    例如,Maybe Int 可以是Just IntNothing。 现在,如果您将一个 Maybe Int 添加到一个 Maybe Int,运算符将检查它们内部是否都是 Just Int,并且如果是这样,将解开 Int ,将它们传递给加法运算符,将生成的 Int 重新包装为新的 Just Int (即一个有效的Maybe Int),从而返回一个Maybe Int。 但如果其中一个是 Nothing ,则该运算符将立即返回 Nothing,这又是一个有效的 Maybe Int。 这样,您就可以假装您的 Maybe Int 只是普通数字并对它们执行常规数学运算。 如果您得到Nothing,您的方程仍会产生正确的结果 - 而无需您到处检查Nothing。 p>

但这个例子正是Maybe 所发生的情况。 如果额外信息是 IO,那么将调用为 IO 定义的特殊运算符,并且它可以在执行加法之前执行完全不同的操作。 (好吧,将两个 IO Int 添加在一起可能是无意义的 – 我还不确定。)(另外,如果您注意了 Maybe 示例,您就会注意到“用额外的东西包装一个值”并不总是正确的,但很难做到准确、正确和精确而不令人费解。)

基本上,“monad”大致意味着“模式”。 但是,您现在拥有的不是一本充满非正式解释和专门命名的模式的书,而是一种语言构造(语法和所有内容),它允许您将新模式声明为程序中的事物。 (这里的不精确之处在于所有模式都必须遵循特定的形式,因此 monad 并不像模式那么通用。但我认为这是大多数人知道和理解的最接近的术语。)

这就是为什么人们发现 monad 如此令人困惑:因为它们是一个通用的概念。 询问什么使某物成为单子与询问什么使某物成为模式同样含糊。

但想一想在语言中提供句法支持对模式概念的影响:您不必阅读Gang of Four书并记住特定模式的构造,您只需编写以不可知的通用方式实现此模式的代码一次,然后就完成了! 然后,您可以重用此模式,例如访问者或策略或外观或其他模式,只需用它装饰代码中的操作即可,而无需一遍又一遍地重新实现它!

这就是为什么理解单子的人发现它们如此有用:这不是知识势利小人以理解为荣的象牙塔概念(好吧,当然也是如此,teehee) ,但实际上使代码更简单。

[Disclaimer: I am still trying to fully grok monads. The following is just what I have understood so far. If it’s wrong, hopefully someone knowledgeable will call me on the carpet.]

Arnar wrote:

Monads are simply a way to wrapping things and provide methods to do operations on the wrapped stuff without unwrapping it.

That’s precisely it. The idea goes like this:

  1. You take some kind of value and wrap it with some additional information. Just like the value is of a certain kind (eg. an integer or a string), so the additional information is of a certain kind.

    E.g., that extra information might be a Maybe or an IO.

  2. Then you have some operators that allow you to operate on the wrapped data while carrying along that additional information. These operators use the additional information to decide how to change the behaviour of the operation on the wrapped value.

    E.g., a Maybe Int can be a Just Int or Nothing. Now, if you add a Maybe Int to a Maybe Int, the operator will check to see if they are both Just Ints inside, and if so, will unwrap the Ints, pass them the addition operator, re-wrap the resulting Int into a new Just Int (which is a valid Maybe Int), and thus return a Maybe Int. But if one of them was a Nothing inside, this operator will just immediately return Nothing, which again is a valid Maybe Int. That way, you can pretend that your Maybe Ints are just normal numbers and perform regular math on them. If you were to get a Nothing, your equations will still produce the right result – without you having to litter checks for Nothing everywhere.

But the example is just what happens for Maybe. If the extra information was an IO, then that special operator defined for IOs would be called instead, and it could do something totally different before performing the addition. (OK, adding two IO Ints together is probably nonsensical – I’m not sure yet.) (Also, if you paid attention to the Maybe example, you have noticed that “wrapping a value with extra stuff” is not always correct. But it’s hard to be exact, correct and precise without being inscrutable.)

Basically, “monad” roughly means “pattern”. But instead of a book full of informally explained and specifically named Patterns, you now have a language construct – syntax and all – that allows you to declare new patterns as things in your program. (The imprecision here is all the patterns have to follow a particular form, so a monad is not quite as generic as a pattern. But I think that’s the closest term that most people know and understand.)

And that is why people find monads so confusing: because they are such a generic concept. To ask what makes something a monad is similarly vague as to ask what makes something a pattern.

But think of the implications of having syntactic support in the language for the idea of a pattern: instead of having to read the Gang of Four book and memorise the construction of a particular pattern, you just write code that implements this pattern in an agnostic, generic way once and then you are done! You can then reuse this pattern, like Visitor or Strategy or Façade or whatever, just by decorating the operations in your code with it, without having to re-implement it over and over!

So that is why people who understand monads find them so useful: it’s not some ivory tower concept that intellectual snobs pride themselves on understanding (OK, that too of course, teehee), but actually makes code simpler.

审判长 2024-07-11 01:01:51

经过一番努力,我想我终于理解了 monad。 在重读了我自己对投票最高的答案的冗长批评后,我将提供这个解释。

要理解 monad,需要回答三个问题:

  1. 为什么需要 monad?
  2. 什么是单子?
  3. monad 是如何实现的?

正如我在最初的评论中指出的那样,太多的 monad 解释陷入了问题 3,而没有真正充分地涵盖问题 2 或问题 1。

为什么需要 monad?

纯函数Haskell 等语言与 C 或 Java 等命令式语言的不同之处在于,纯函数式程序不一定按特定顺序、一次一步执行。 Haskell 程序更类似于数学函数,您可以在其中以任意数量的潜在阶数求解“方程”。 这带来了许多好处,其中之一是它消除了某些类型错误的可能性,特别是与“状态”等事物相关的错误。

然而,有些问题并不是用这种编程风格那么简单就能解决的。 有些事情(例如控制台编程和文件 I/O)需要按特定顺序发生,或者需要维护状态。 处理这个问题的一种方法是创建一种表示计算状态的对象,以及一系列将状态对象作为输入并返回新的修改后的状态对象的函数。

因此,让我们创建一个假设的“状态”值,它表示控制台屏幕的状态。 这个值到底是如何构造的并不重要,但假设它是一个字节长度 ascii 字符的数组,表示当前在屏幕上可见的内容,以及一个表示用户以伪代码形式输入的最后一行输入的数组。 我们定义了一些获取控制台状态、修改它并返回新控制台状态的函数。

consolestate MyConsole = new consolestate;

因此,要进行控制台编程,但以纯函数方式,您需要在彼此之间嵌套大量函数调用。

consolestate FinalConsole = print(input(print(myconsole, "Hello, what's your name?")),"hello, %inputbuffer%!");

以这种方式编程保持了“纯粹”的功能风格,同时强制对控制台的更改按特定顺序发生。 但是,我们可能想要一次执行更多操作,如上面的示例所示。 以这种方式嵌套函数将开始变得笨拙。 我们想要的是基本上与上面做同样事情的代码,但写得更像这样:

consolestate FinalConsole = myconsole:
                            print("Hello, what's your name?"):
                            input():
                            print("hello, %inputbuffer%!");

这确实是一种更方便的编写方式。 但我们该怎么做呢?

什么是 monad?

一旦您定义了一种类型(例如 consolestate)以及一组专门设计用于操作该类型的函数,您就可以将通过定义像 : (绑定)这样的运算符,将这些东西的整个包集成到一个“monad”中,该运算符自动将左侧的返回值提供给右侧的函数参数,以及一个 lift 运算符将普通函数转换为与特定类型的绑定运算符一起使用的函数。

单子是如何实现的?

请参阅其他答案,这些答案似乎很容易跳入其中的细节。

After much striving, I think I finally understand the monad. After rereading my own lengthy critique of the overwhelmingly top voted answer, I will offer this explanation.

There are three questions that need to be answered to understand monads:

  1. Why do you need a monad?
  2. What is a monad?
  3. How is a monad implemented?

As I noted in my original comments, too many monad explanations get caught up in question number 3, without, and before really adequately covering question 2, or question 1.

Why do you need a monad?

Pure functional languages like Haskell are different from imperative languages like C, or Java in that, a pure functional program is not necessarily executed in a specific order, one step at a time. A Haskell program is more akin to a mathematical function, in which you may solve the "equation" in any number of potential orders. This confers a number of benefits, among which is that it eliminates the possibility of certain kinds of bugs, particularly those relating to things like "state".

However, there are certain problems that are not so straightforward to solve with this style of programming. Some things, like console programming, and file i/o, need things to happen in a particular order, or need to maintain state. One way to deal with this problem is to create a kind of object that represents the state of a computation, and a series of functions that take a state object as input, and return a new modified state object.

So let's create a hypothetical "state" value, that represents the state of a console screen. exactly how this value is constructed is not important, but let's say it's an array of byte length ascii characters that represents what is currently visible on the screen, and an array that represents the last line of input entered by the user, in pseudocode. We've defined some functions that take console state, modify it, and return a new console state.

consolestate MyConsole = new consolestate;

So to do console programming, but in a pure functional manner, you would need to nest a lot of function calls inside eachother.

consolestate FinalConsole = print(input(print(myconsole, "Hello, what's your name?")),"hello, %inputbuffer%!");

Programming in this way keeps the "pure" functional style, while forcing changes to the console to happen in a particular order. But, we'll probably want to do more than just a few operations at a time like in the above example. Nesting functions in that way will start to become ungainly. What we want, is code that does essentially the same thing as above, but is written a bit more like this:

consolestate FinalConsole = myconsole:
                            print("Hello, what's your name?"):
                            input():
                            print("hello, %inputbuffer%!");

This would indeed be a more convenient way to write it. How do we do that though?

What is a monad?

Once you have a type (such as consolestate) that you define along with a bunch of functions designed specifically to operate on that type, you can turn the whole package of these things into a "monad" by defining an operator like : (bind) that automatically feeds return values on its left, into function parameters on its right, and a lift operator that turns normal functions, into functions that work with that specific kind of bind operator.

How is a monad implemented?

See other answers, that seem quite free to jump into the details of that.

辞别 2024-07-11 01:01:51

在几年前回答这个问题后,我相信我可以通过以下方式改进和简化该响应:

monad 是一种函数组合技术,它使用组合函数 bindbind 来具体化某些输入场景的处理code>,在合成过程中预处理输入。

在正常组合中,函数 compose (>>) 用于将组合函数按顺序应用于其前一个函数的结果。 重要的是,所组成的函数需要处理其输入的所有场景。

<代码>(x -> y) >> (y -> z)

这种设计可以通过重构输入来改进,以便更容易地询问相关状态。 因此,该值可以变为 Mb,而不是简单的 y,例如,(is_OK, b) if y code> 包含有效性的概念。

例如,当输入仅可能是数字时,您可以将类型重组为 bool ,指示是否存在有效数字和元组中的数字,例如 bool * float。 组合函数现在不再需要解析输入字符串来确定数字是否存在,而只需检查元组的 bool 部分。

(Ma -> Mb) >>> (Mb -> Mc)

这里,再次,组合自然地发生在 compose 中,因此每个函数必须单独处理其输入的所有场景,尽管现在这样做要容易得多。

然而,如果我们能够在处理场景成为例行公事的情况下将审讯工作具体化,结果会怎样呢? 例如,如果当输入不正确时我们的程序什么也不做,就像当 is_OKfalse 时一样。 如果这样做了,那么组合函数将不需要自己处理该场景,从而极大地简化了代码并实现了另一个级别的重用。

为了实现这种外部化,我们可以使用函数 bind (>>=) 来执行 composition 而不是 compose。 因此,不是简单地将值从一个函数的输出传输到另一个 Bind 的输入,而是检查 MaM 部分并决定是否以及如何将组合函数应用于 a。 当然,函数 bind 将为我们特定的 M 专门定义,以便能够检查其结构并执行我们想要的任何类型的应用程序。 尽管如此,a 可以是任何东西,因为 bind 仅在确定应用程序需要时将未经检查的 a 传递给组合函数。 此外,组合函数本身也不再需要处理输入结构的 M 部分,从而简化了它们。 因此...

(a -> Mb) >>= (b -> Mc) 或者更简洁地Mb >>= (b -> Mc)< 简而言之,一旦输入被设计为充分暴露某些输入场景,

单子就会外化,从而提供围绕某些输入场景的处理的标准行为。 此设计是一个shell 和内容 模型,其中shell 包含与组合函数的应用程序相关的数据,并且由bind 函数询问并且仅对bind 函数可用。

因此,一个 monad 是由三部分组成:

  1. 一个用于保存 monad 相关信息的 M shell,
  2. 一个用于在组合函数的应用程序中使用此 shell 信息来实现的 bind 函数。它在 shell 中找到的内容值,以及
  3. 表单的可组合函数,a ->; Mb,生成包含单子管理数据的结果。

一般来说,函数的输入比其输出具有更多限制,输出可能包括错误条件等; 因此,Mb 结果结构通常非常有用。 例如,当除数为 0 时,除法运算符不会返回数字。

此外,monad 可能包括将值 a 包装到单子类型 Ma 中的包装函数,以及通用函数 a -> b,转化为一元函数,a -> Mb,通过在应用程序后包装其结果。 当然,与bind一样,此类包装函数是M特有的。 一个例子:

let return a = [a]
let lift f a = return (f a)

bind 函数的设计假定了不可变的数据结构和纯函数,否则事情会变得复杂并且无法做出保证。 因此,存在一元法则:

给定...

M_ 
return = (a -> Ma)
f = (a -> Mb)
g = (b -> Mc)

则...

Left Identity  : (return a) >>= f === f a
Right Identity : Ma >>= return    === Ma
Associative    : Ma >>= (f >>= g) === Ma >>= ((fun x -> f x) >>= g)

关联性意味着无论bind何时,bind都会保留求值的顺序被申请;被应用。 也就是说,在上面关联性的定义中,强制对fg的括号绑定进行早期评估只会产生一个需要 Ma 来完成 bind 的函数。 因此,必须先确定 Ma 的计算结果,然后才能将其值应用于 f,并将结果依次应用于 g

After giving an answer to this question a few years ago, I believe I can improve and simplify that response with...

A monad is a function composition technique that externalizes treatment for some input scenarios using a composing function, bind, to pre-process input during composition.

In normal composition, the function, compose (>>), is use to apply the composed function to the result of its predecessor in sequence. Importantly, the function being composed is required to handle all scenarios of its input.

(x -> y) >> (y -> z)

This design can be improved by restructuring the input so that relevant states are more easily interrogated. So, instead of simply y the value can become Mb such as, for instance, (is_OK, b) if y included a notion of validity.

For example, when the input is only possibly a number, instead of returning a string which can dutifully contain a number or not, you could restructure the type into a bool indicating the presence of a valid number and a number in tuple such as, bool * float. The composed functions would now no longer need to parse an input string to determine whether a number exists but could merely inspect the bool portion of a tuple.

(Ma -> Mb) >> (Mb -> Mc)

Here, again, composition occurs naturally with compose and so each function must handle all scenarios of its input individually, though doing so is now much easier.

However, what if we could externalize the effort of interrogation for those times where handling a scenario is routine. For example, what if our program does nothing when the input is not OK as in when is_OK is false. If that were done then composed functions would not need to handle that scenario themselves, dramatically simplifying their code and effecting another level of reuse.

To achieve this externalization we could use a function, bind (>>=), to perform the composition instead of compose. As such, instead of simply transferring values from the output of one function to the input of another Bind would inspect the M portion of Ma and decide whether and how to apply the composed function to the a. Of course, the function bind would be defined specifically for our particular M so as to be able to inspect its structure and perform whatever type of application we want. Nonetheless, the a can be anything since bind merely passes the a uninspected to the the composed function when it determines application necessary. Additionally, the composed functions themselves no longer need to deal with the M portion of the input structure either, simplifying them. Hence...

(a -> Mb) >>= (b -> Mc) or more succinctly Mb >>= (b -> Mc)

In short, a monad externalizes and thereby provides standard behaviour around the treatment of certain input scenarios once the input becomes designed to sufficiently expose them. This design is a shell and content model where the shell contains data relevant to the application of the composed function and is interrogated by and remains only available to the bind function.

Therefore, a monad is three things:

  1. an M shell for holding monad relevant information,
  2. a bind function implemented to make use of this shell information in its application of the composed functions to the content value(s) it finds within the shell, and
  3. composable functions of the form, a -> Mb, producing results that include monadic management data.

Generally speaking, the input to a function is far more restrictive than its output which may include such things as error conditions; hence, the Mb result structure is generally very useful. For instance, the division operator does not return a number when the divisor is 0.

Additionally, monads may include wrap functions that wrap values, a, into the monadic type, Ma, and general functions, a -> b, into monadic functions, a -> Mb, by wrapping their results after application. Of course, like bind, such wrap functions are specific to M. An example:

let return a = [a]
let lift f a = return (f a)

The design of the bind function presumes immutable data structures and pure functions others things get complex and guarantees cannot be made. As such, there are monadic laws:

Given...

M_ 
return = (a -> Ma)
f = (a -> Mb)
g = (b -> Mc)

Then...

Left Identity  : (return a) >>= f === f a
Right Identity : Ma >>= return    === Ma
Associative    : Ma >>= (f >>= g) === Ma >>= ((fun x -> f x) >>= g)

Associativity means that bind preserves the order of evaluation regardless of when bind is applied. That is, in the definition of Associativity above, the force early evaluation of the parenthesized binding of f and g will only result in a function that expects Ma in order to complete the bind. Hence the evaluation of Ma must be determined before its value can become applied to f and that result in turn applied to g.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文