关于haskell:什么是monad?

What is a monad?

在最近短暂地看了哈斯克尔之后,对于单子的本质是什么,一个简短、简洁、实用的解释是什么?

我发现我遇到的大多数解释都很难理解,而且缺乏实际细节。


第一:如果你不是数学家,Monad这个词有点空虚。另一个可供选择的术语是计算生成器,它更能描述它们的实际用途。好的。

你需要一些实际的例子:好的。

例1:列表理解:好的。

1
[x*2 | x<-[1..10], odd x]

此表达式返回1到10范围内所有奇数的双精度数。非常有用!好的。

事实证明,对于列表monad中的某些操作,这实际上只是语法上的糖分。同样的列表理解可以写成:好的。

1
2
3
4
do
   x <- [1..10]
   guard (odd x)
   return (x * 2)

甚至:好的。

1
[1..10] >>= (\x -> guard (odd x) >> return (x*2))

例2:输入/输出:好的。

1
2
3
4
do
   putStrLn"What is your name?"
   name <- getLine
   putStrLn ("Welcome," ++ name ++"!")

这两个例子都使用monad,也就是计算生成器。共同的主题是Monad链以某种特定的、有用的方式进行操作。在列表理解中,操作被链接起来,这样,如果一个操作返回一个列表,那么对列表中的每个项目执行以下操作。另一方面,IO monad按顺序执行操作,但同时传递一个"隐藏变量",表示"世界状态",允许我们以纯粹的功能方式编写I/O代码。好的。

结果表明,链接操作模式非常有用,并且在Haskell中用于许多不同的事情。好的。

另一个例子是异常:使用Errormonad,操作被链接,这样就可以按顺序执行,除非抛出了错误,在这种情况下,链的其余部分被放弃。好的。

列表理解语法和do符号都是使用>>=运算符链接操作的语法甜品。monad基本上只是一种支持>>=操作符的类型。好的。

示例3:解析器好的。

这是一个非常简单的解析器,可以解析带引号的字符串或数字:好的。

1
2
3
4
5
6
7
8
9
10
11
parseExpr = parseString <|> parseNumber

parseString = do
        char '"'
        x <- many (noneOf"
"")
        char '"'
        return (StringValue x)

parseNumber = do
    num <- many1 digit
    return (NumberValue (read num))

chardigit等操作相当简单。它们要么匹配要么不匹配。魔力是管理控制流的monad:操作按顺序执行,直到匹配失败,在这种情况下,monad会返回到最新的<|>,并尝试下一个选项。同样,使用一些附加的、有用的语义链接操作的方法。好的。

示例4:异步编程好的。

上面的例子在haskell中,但事实证明f也支持monads。这个例子是从Don Syme那里偷来的:好的。

1
2
3
4
5
6
let AsyncHttp(url:string) =
    async {  let req = WebRequest.Create(url)
             let! rsp = req.GetResponseAsync()
             use stream = rsp.GetResponseStream()
             use reader = new System.IO.StreamReader(stream)
             return reader.ReadToEnd() }

此方法获取网页。穿孔行是使用GetResponseAsync—它实际上在单独的线程上等待响应,而主线程则从函数返回。最后三行在收到响应时在生成的线程上执行。好的。

在大多数其他语言中,您必须为处理响应的行显式地创建一个单独的函数。asyncMonad能够自行"拆分"该块,并推迟后半块的执行。(async {}语法表示块中的控制流是由asyncmonad定义的。)好的。

它们是如何工作的好的。

那么Monad怎么能做所有这些花哨的控制流的事情呢?在do块(或在f中调用的计算表达式)中实际发生的情况是,每个操作(基本上是每一行)都被包装在一个单独的匿名函数中。然后,使用bind运算符(haskell中拼写为>>=)组合这些函数。由于bind操作组合了函数,因此它可以按自己认为合适的方式执行它们:顺序地、多次地、逆向地、丢弃一些函数、在感觉合适的时候在单独的线程上执行一些函数等等。好的。

例如,这是示例2中IO代码的扩展版本:好的。

1
2
3
4
putStrLn"What is your name?"
>>= (\_ -> getLine)
>>= (
ame -> putStrLn ("Welcome," ++ name ++"!"))

这更难看,但实际上发生的事情也更明显。>>=运算符是一个神奇的组成部分:它获取一个值(在左侧)并将其与一个函数(在右侧)组合,以产生一个新的值。然后,下一个>>=运算符获取这个新值,并再次与函数组合以生成新值。>>=可以被看作是一个小型评估器。好的。

注意,不同类型的>>=是重载的,因此每个monad都有自己的>>=实现。(但是,链中的所有操作都必须是同一个monad类型,否则>>=运算符将无法工作。)好的。

>>=的最简单的可能实现就是取左边的值并将其应用到右边的函数并返回结果,但是正如前面所说,当monad的>>=的实现中有额外的事情发生时,整个模式才有用。好的。

在如何将值从一个操作传递到下一个操作上还有一些额外的聪明之处,但这需要对haskell类型系统进行更深入的解释。好的。

总结好的。

在haskell术语中,monad是一个参数化类型,它是monad类型类的一个实例,monad类定义>>=以及一些其他运算符。用外行的术语来说,monad只是定义>>=操作的一种类型。好的。

本身,>>=只是一种链接函数的繁琐方法,但是随着隐藏"管道"的do符号的出现,单元操作被证明是一种非常好和有用的抽象,在语言中的许多地方都很有用,并且对于在语言中创建自己的小型语言非常有用。好的。

为什么单子很难?好的。型

对于哈斯克尔的许多学习者来说,单子是他们遇到的障碍,就像砖墙一样。不是monad本身很复杂,而是实现依赖于许多其他高级的haskell特性,比如参数化类型、类型类等等。问题是haskell I/O是基于monads的,I/O可能是学习一种新语言时首先要了解的内容之一——毕竟,创建不产生任何输出的程序并没有多大乐趣。对于这个鸡和蛋的问题,我没有立即的解决方案,除非把I/O当作"魔法在这里发生",直到你对语言的其他部分有足够的经验为止。对不起的。好的。型

Monads上的优秀博客:http://adit.io/posts/2013-04-17-functors,_applications,_and_monads_in_pictures.html好的。型好啊。


解释"Monad是什么"有点像说"数字是什么?"我们总是用数字。但想象一下你遇到了一个对数字一无所知的人。你怎么解释这些数字?你怎么开始描述为什么这可能有用?

什么是单子?简而言之:这是一种将操作链接在一起的特定方法。

本质上,您正在编写执行步骤,并将它们与"绑定函数"链接在一起。(在haskell中,它名为>>=)您可以自己编写对绑定操作符的调用,也可以使用语法sugar让编译器为您插入那些函数调用。但是不管怎样,每个步骤都是通过调用这个绑定函数来分隔的。

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

听起来不太难,对吧?但是有不止一种单子。为什么?怎么用?

好吧,bind函数只需从一个步骤中获取结果,并将其提供给下一个步骤。但如果这就是蒙娜德所做的一切…这实际上不是很有用。这一点很重要,要理解:每一个有用的单子除了做单子之外,还做其他的事情。每一个有用的单子都有一种"特殊的力量",这使它独一无二。

(一个没有什么特别之处的单子被称为"身份单子"。这听起来像是一个毫无意义的东西,但结果却不是…但这是另一个故事&trade;)

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

  • 如果每个步骤都返回一个成功/失败指示器,那么只有在前一个步骤成功时,才能让bind执行下一个步骤。这样,失败的步骤将"自动"中止整个序列,而无需您进行任何条件测试。(失败的单子。)

  • 扩展这个概念,您可以实现"异常"。(错误Monad或异常Monad。)因为您自己定义它们,而不是将其作为语言功能,所以您可以定义它们的工作方式。(例如,您可能希望忽略前两个异常,只有在引发第三个异常时才会中止。)

  • 您可以使每个步骤返回多个结果,并让绑定函数循环遍历这些结果,将每个结果送入下一步。这样,在处理多个结果时,就不必一直在各地编写循环。绑定函数"自动"为您完成所有这些。(单子上的单子。)

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

  • 您可以这样做,以便替换"额外数据"。这允许您模拟破坏性更新,而实际上不进行破坏性更新。(州里的蒙娜德和它的表兄,作家蒙娜德。)

  • 因为你只是在模拟破坏性的更新,你可以做一些真正破坏性的更新是不可能的事情。例如,您可以撤消上一次更新,或者恢复到旧版本。

  • 你可以制作一个单子,在那里计算可以暂停,所以你可以暂停你的程序,进入并修补内部状态数据,然后继续它。

  • 您可以将"continuations"实现为monad。这可以让你打破人们的想法!

所有这些和更多的都可以用单子。当然,没有单子,所有这些都是完全可能的。使用Monads非常简单。


事实上,与蒙娜斯的共同理解相反,他们与国家无关。monad只是包装东西的一种方法,它提供了在不拆开包装的情况下对包装的东西进行操作的方法。

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

1
data Wrapped a = Wrap a

包装我们定义的东西

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

要在不展开的情况下执行操作,假设您有一个函数f :: a -> b,那么您可以这样做来提升该函数以对包装值执行操作:

1
2
fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)

这就是要理解的一切。然而,事实证明,这种提升有一个更一般的功能,即bind

1
2
bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x

bindfmap能做得多一点,但反过来不行。实际上,fmap只能用bindreturn来定义。所以,当定义Monad时……你给出它的类型(这里是Wrapped a,然后说它的returnbind操作是如何工作的。

很酷的是,这是一个非常普通的模式,它会突然出现在所有地方,以一种纯粹的方式封装状态只是其中之一。

关于如何使用monad来引入功能依赖性,从而控制评估顺序的一篇好文章,就像在Haskell的IO monad中使用的一样,请查看内部的IO。

至于了解单子,不要太担心。读一些你觉得有趣的东西,如果你不马上理解的话,不要担心。然后,像哈斯克尔这样的语言潜水就是一条出路。单子就是这样的东西之一,通过练习,理解会慢慢进入你的大脑,有一天你突然意识到你理解了它们。


但是,你本可以发明单子的!

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.

< /块引用>


monad是具有两个操作的数据类型:>>=(aka bind)和return(aka unit)。return采用任意值并创建monad实例。>>=以monad为例,并在其上映射一个函数。(您已经看到monad是一种奇怪的数据类型,因为在大多数编程语言中,您无法编写一个接受任意值并从中创建类型的函数。monads使用一种参数多态性。)

在haskell表示法中,monad接口被写入

1
2
3
class Monad m where
  return :: a -> m a
  (>>=) :: forall a b . m a -> (a -> m b) -> m b

这些操作应该遵守某些"法律",但这并不十分重要:"法律"只是编纂了操作的合理实施方式(基本上,>>=return应该就值如何转换为单实例达成一致,>>=是关联的)。

monad不仅仅与状态和I/O有关:它们抽象了一种通用的计算模式,包括处理状态、I/O、异常和非确定性。可能最简单的单子是列表和选项类型:

1
2
3
4
5
6
7
8
9
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构造器。这两个单体都封装了各自数据类型上的通用和有用的计算模式(注意,两者都与副作用或I/O无关)。

你真的需要花点时间写一些不平凡的haskell代码来理解monads是关于什么以及为什么它们是有用的。


你首先应该理解什么是函子。在此之前,请理解高阶函数。

高阶函数只是一个以函数为参数的函数。

函式是存在一个高阶函数的任何类型构造T,称为map,它将a -> b类型的函数(给定ab两种类型)转换为T a -> T b的函数。此map函数还必须遵守同一性和组合律,以便下列表达式对所有pq都返回true(haskell表示法):

1
2
map id = id
map (p . q) = map p . map q

例如,如果一个名为List的类型构造函数配备了一个符合上述规律的(a -> b) -> List a -> List b类型函数,那么它就是一个函数。唯一实际的实现是显而易见的。生成的List a -> List b函数迭代给定的列表,为每个元素调用(a -> b)函数,并返回结果列表。

monad本质上只是一个函数T,有两种额外的方法:joinT (T a) -> T a型和unit型(有时称为returnforkpurea -> T a型。对于haskell中的列表:

1
2
join :: [[a]] -> [a]
pure :: a -> [a]

为什么有用?因为您可以,例如,使用返回列表的函数对一个列表执行mapjoin获取结果列表并将其连接起来。List是单胞菌,因为这是可能的。

您可以编写执行map的函数,然后编写join的函数。此函数称为bindflatMap(>>=)(=<<)。这通常是haskell中给出monad实例的方式。

monad必须满足某些规律,即join必须是关联的。这意味着,如果您有[[[a]]]类型的值x,那么join (join x)应该等于join (map join x)。并且pure必须是join的身份,这样join (pure x) == x就可以了。


[ disclaimer:我还想充分理解的单子。以下就是我的父亲有understood母猪。如果真的错了,hopefully knowledgeable会有人打电话给我的地毯。

arnar写道:

Monads are simply a way to wrapping things and provide methods to do operations on the wrapped stuff without unwrapping it.

而正是这样的。的想法是这样的:

  • 你把这种价值和把它与一些额外的信息。是的,就像《价值某冰颊(EG。安整数或字符串)的附加信息,所以冰某的脸颊。

    例如,这可能是一个额外的信息MaybeIO

  • 你有一些运营商,允许你操作的数据包。沿这段额外的信息。这些运营商使用的附加信息的两个《决定如何改变行为的操作在包装的价值。

    例如,可以Maybe IntA或A Just IntNothing。现在,如果你添加一Maybe Int两个Maybe Int放映会的检查,如果他们看到的都是Just Ints里面,如果这样,将unwrap《Ints,通过他们的加法操作,重新包装的resulting Int到一个新的Just Int(这是一个有效的Maybe Int),和一Maybe Int这样的回报。但如果他们是一个一个Nothing在里面,这只是运营商将立即返回Nothing了,这是一个有效的Maybe Int。这样的方式,你可以pretend,你是完全正常的Maybe Ints数和perform常规数学对他们。如果你是一个让你Nothing方程,将静态的生产权,没有结果,你有两个站Nothing所有支票。

  • 但以冰为Maybe真的会发生什么。如果我是个IO额外的信息,然后,定义为IOs特殊的放映会,而不是所谓的,它可以做一些完全不同的表演过《之外。(好吧,第二IO Ints增冰可能nonsensical——我不确定,我(也)。注意,如果你支付的Maybeexample,你有noticed"包装,A值与冰的额外的东西",并不总是正确的。但它的硬的精确和准确的,正确的,没有被inscrutable)。

    basically"单子"roughly均值的"模式"。但是,而不是一本书的全鸭informally explained专门命名方式,你现在有一个语言的语法构造和所有————这是允许你为新模式的两个DECLARE的事情在你的程序。(《冰imprecision这里所有的方式有两种,一种特殊的单子,所以A是不通用的,作为一个全新的AA型。但我认为这是closest术语,大多数人知道和了解。)

    这就是为什么人们找到冰渣confusing单子:因为他们是这样一种通用的概念。问:是什么让一个单子的冰similarly浪潮为问是什么让一个A型。

    我觉得这个含义大学具有句法的语言支持的想法(而不是A型:具有两个读《四人帮》和《建设部memorise特别是A型,你只要写代码,implements这个模式在一个通用的agnostic通一次,然后你做了!这是你可以重用的类型、游客或战略或什么的?艾德或什么的,只是在城市作战decorating用它在你的代码中,有两个没有重新实施它的鸭子!

    所以这是为什么中国人了解它们,所以找到有用的单子:这不是象牙塔的一些概念,snobs自豪自己对知识的理解(这也是好的,当然,teehee),但实际上使简单的代码。


    经过努力,我想我终于明白了单子。在重读了我自己对压倒性的最高投票答案的冗长批评之后,我将给出这个解释。

    要理解单子,需要回答三个问题:

  • 你为什么需要一个单子?
  • 什么是单子?
  • Monad是如何实现的?
  • 正如我在最初的评论中所指出的,太多的单子解释在问题3中被卷入,没有,而且在真正充分涵盖问题2或问题1之前。

    你为什么需要一个单子?

    像Haskell这样的纯函数语言与命令语言(如C或Java)不同,纯函数程序不一定按特定的顺序执行,一次一步。haskell程序更类似于一个数学函数,在这个函数中,你可以用任意数量的势序来解"方程"。这带来了许多好处,其中包括消除了某些类型的错误的可能性,特别是与"状态"等相关的错误。

    但是,有一些问题并不能用这种编程方式直接解决。有些事情,比如控制台编程和文件I/O,需要以特定的顺序发生,或者需要保持状态。处理这个问题的一种方法是创建一种表示计算状态的对象,以及一系列以状态对象为输入的函数,并返回一个新的修改过的状态对象。

    因此,让我们创建一个假设的"状态"值,它表示控制台屏幕的状态。这个值的构造方式并不重要,但我们假设它是一个字节长度的ASCII字符数组,表示屏幕上当前可见的内容,以及一个数组,表示用户输入的最后一行伪代码。我们已经定义了一些接受控制台状态、修改它并返回新控制台状态的函数。

    1
    consolestate MyConsole = new consolestate;

    因此,要进行控制台编程,但是以一种纯粹的函数方式,您需要在每个对象内部嵌套大量的函数调用。

    1
    consolestate FinalConsole = print(input(print(myconsole,"Hello, what's your name?")),"hello, %inputbuffer%!");

    以这种方式编程可以保持"纯粹"的功能风格,同时强制以特定的顺序对控制台进行更改。但是,我们可能希望像上面的例子一样,一次只做几个操作。这样的嵌套函数将开始变得笨拙。我们想要的是基本上和上面一样的代码,但是写得更像这样:

    1
    2
    3
    4
    consolestate FinalConsole = myconsole:
                                print("Hello, what's your name?"):
                                input():
                                print("hello, %inputbuffer%!");

    这确实是一种更方便的书写方式。但我们该怎么做呢?

    什么是单子?

    一旦您定义了一个类型(如consolestate)以及一系列专门针对该类型进行操作的函数,您就可以通过定义一个像:(bind)这样的运算符,将这些内容的整个包转换为一个"monad",该运算符会自动将其左侧的返回值输入到右侧的函数参数中,并且lift运算符,它将普通函数转换为与特定类型的绑定运算符一起工作的函数。

    Monad是如何实现的?

    看看其他的答案,这似乎很容易跳到细节。


    (另请参见"Monad是什么?"

    一个很好的单子动机是sigfpe(dan piponi)的单子你可以发明!(也许你已经有了)。有很多其他的单子教程,其中许多错误地试图用各种类比来用"简单的术语"解释单子:这是单子教程的谬误;避免它们。

    正如麦基弗博士在《告诉我们为什么你的语言很烂》一书中所说的:

    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.

    你说你理解也许是单子?很好,你在路上。你只要开始使用其他单子,迟早你会明白什么是单子。

    [如果你是以数学为导向的,你可能想忽略几十个教程并学习其定义,或者遵循类别理论中的讲座:)定义的主要部分是monad m包含一个"类型构造函数",它为每个现有类型"t"定义一个新类型"m t",以及在"常规"类型和"m"类型之间来回切换的一些方法。]

    另外,令人惊讶的是,Monads最好的介绍之一实际上是介绍Monads的早期学术论文之一,PhilipWadler的函数编程Monads。它实际上有一些实用的,非琐碎的激励例子,不像外面的许多人工教程。


    我写这篇文章主要是为了我,但我希望其他人觉得它有用:)好的。

    我相信这个解释更正确。然而,我认为这种治疗仍然是有价值的,并将考虑在以后纳入它。可以这么说,当传统的函数组合处理普通值函数时,monad是关于组合对函数值(高阶函数)进行操作的函数。在处理高阶函数(接受或返回函数的函数)时,必须自定义或定制组合,以便在计算组合时评估操作数。这种评估过程可能很奇特,例如收集异步过程的结果。尽管如此,这种裁剪还是可以按照一定的模式进行。该模式的一个版本称为monad,并遵循非常多的代数加法。特别是,对于以下内容,这种高阶函数将被视为表达式中的数学运算符,接受其他部分应用的运算符,因此,1+ ( 2* ( 3/ ( 7+ (..) ) ) )中的函数1+2*、3/、7+。好的。

    monads处理的问题在算术中也显示为被零除,DivByZero。具体地说,涉及除法的计算必须检测或允许DivByZero例外。这一要求使得在一般情况下对此类表达式进行编码变得混乱。好的。

    一元解决方案是通过执行以下操作来接受DivByZero。好的。

  • 扩展Number类型,将DivByZero作为一个非常规数字的特定值包括:NaNInfinityNull。我们称这个新的数字类型为Nullable
  • 提供"提升"或将现有的Number包装成Nullable类型的功能("包装"的概念是内容Number或值可以"拆开"而不会丢失信息)。
  • 提供"提升"功能或将Number上的现有操作员包装成在Nullable上运行的版本。这样的结果"提升"操作员可能只会执行以下操作:
  • unwrap提供Nullable个操作数,并将其包含的Number个操作数应用于这些操作数,然后将生成的Number个结果"提升"为Nullable
  • 在评估和旁路进一步评估期间检测DivByZero操作数或异常,生成DivByZero值作为结果断言(1 + Null = Null)。然而,要采取什么行动取决于程序员。一般来说,这些包装器函数是编写monad的许多功能的地方。一元状态信息保存在包装器类型本身中,包装函数从中检查,并按照函数编程不可变性方法构造新的一元值。在Nullable的情况下,这种单态信息将描述DivByZero或实际Number是否存在。
  • 因此,monad是一个扩展类型,它包含一个将原始类型"包装"到这个扩展版本的函数,以及另一个包装原始运算符的函数,这样它们就可以处理这个新的扩展类型。(monads可能是泛型或类型参数的动机。)好的。

    事实证明,monad处理不仅可以平滑处理DivByZero(如果可以的话,可以是无穷大),而且可以广泛应用于从类型扩展中受益的情况,以简化编码。事实上,这种适用性似乎很广。好的。

    例如,从字面上看,IO Monad是一种代表宇宙的类型。其目的是认识到原型HelloWorld程序返回的值并不是完全由string的结果类型及其值"hello world"描述的。.实际上,这样的结果还包括对控制台等设备的硬件和内存状态的修改。例如,执行之后,控制台现在显示额外的文本,光标位于新行上,以此类推。如果你愿意的话,IO Monad仅仅是对这种外部效应或副作用的明确认识。好的。

    何苦?好的。

    monad允许设计严格的无状态算法,并记录状态完整的算法。全状态机器很复杂。例如,只有10位的机器可能处于2^10的可能状态。消除多余的复杂性是函数语言的理想选择。好的。

    变量保持状态。消除"变量"应该很简单。纯功能程序不处理变量,只处理值(尽管haskell文档中使用了"variable"一词),而是根据需要使用标签、符号或名称来处理这些值。因此,与纯函数语言中的变量最接近的是函数在每次调用时接受新值时接收的参数。(标签指的是一个值,而变量指的是一个值所在的位置。因此,可以修改变量的内容,但标签是内容本身。最终,最好是给一个苹果,而不是一个装着苹果的袋子。)好的。

    没有变量是纯函数语言使用递归而不是循环进行迭代的原因。递增计数器的操作涉及到使用一个变为递增的变量,以及所有不确定因素,包括如何更新、测试时、它应该是什么值以及何时更新,以及当多个线程可能访问同一个变量时的复杂性。好的。

    不过,那又怎样?好的。

    如果没有国家的存在,一个函数就必须成为一个结果的声明或定义,与某个潜在国家对一个结果的登记相反。本质上,incFun(x) = x + 1的函数表达式比incImp(x) = x.add(1); return x;的命令表达式简单,这里,incFun不修改x而是创造一个新的值。甚至可以用它在表达式中的定义来代替incfun,比如1 + incFun(x)变为1 + (x + 1)。另一方面,incImp修改了x的状态。对于x而言,无论这种修改意味着什么,都可能是不清楚的,并且最终除了任何并发问题之外,如果不执行程序,就无法确定。好的。

    随着时间的推移,这种复杂性的认知代价越来越高(2^n)。相反,操作者+不能修改x而是必须构造一个新值,其结果仅限于并完全由x1的值和+的定义决定。特别是,避免了2^n复杂性爆炸。此外,为了强调并发性,与incFun不同,incImp不能在没有关于参数共享的预防措施的情况下并发调用,因为它会被每次调用修改。好的。

    为什么叫它Monad?好的。

    单子的特点是一个数学结构称为单子从代数群理论。这就是说,它的意思是一个单体具有以下三个性质:好的。

  • 具有二元运算符*,因此x, y, and zx * y = z属于某种类型的S。例如1÷;2=0.5,其中1、2和0.5都是Number类型。关闭
  • 有一个标识元素,i,与二元运算符关联,该二元运算符不执行任何操作,因此(i * x) = (x * i) = x。例如,数字运算符"+"和数字"0",在4+0=0+4=4中。身份
  • "分段"的评价顺序是不相关的:(x * y) * z = x * (y * z)。例如,(3 + 4) + 12 = 3 + (4 + 12) = 19中的数字运算符"+"。但是请注意,术语的顺序不能改变。结合性
  • 属性3(关联性)允许通过将任意长度的表达式划分为段并单独计算每个段(如并行)来计算。例如,x1*x2*...*xN可以分割成(x1..xJ) * (xJ+1...xM) * (xM+1...xN)。然后可以收集单独的结果x0J * xJM * xMN,并进行类似的进一步评估。支持这样的分割是一项关键技术,可以确保谷歌的分布式搜索算法(LA map/reduce)使用的正确并发性和分布式评估。好的。

    属性2(标识)允许以各种方式更容易地构造表达式,尽管它可能并不完全明显;但是,同样地,对于早期计数系统来说,零显然不是必需的,它作为空的概念和包装空值一样有用。注意,在类型Nullable中,Null不是空值,而是DivByZero的值。具体来说,nn + DivByZero = DivByZeronn + 0 = 0 + nn = nn因此0仍然是+下的身份,其中nn是任何Nullable下的身份。好的。

    最后,我们不再使用罗马数字是有原因的……没有对零或分数、无理数、负数、虚数的扩展适应……是的,似乎我们的数字系统可以被视为单元数。好的。好啊。


    monad实际上是"类型运算符"的一种形式。它可以做三件事。首先,它将"包装"(或以其他方式转换)一个类型的值到另一个类型(通常称为"单类型")。其次,它将使底层类型上的所有操作(或函数)都在一元类型上可用。最后,它将支持将自己与另一个单子结合起来生成复合单子。

    在VisualBasic/C中,"maybe monad"本质上等同于"可以为空的类型"。它接受一个不可为空的类型"t",并将其转换为"nullable",然后定义所有二进制运算符在nullable上的含义。

    副作用的表现类似。创建了一个结构,它将副作用的描述与函数的返回值放在一起。然后,"提升"操作在函数之间传递值时复制副作用。

    它们被称为"monads",而不是更容易理解"类型运算符"的名称,原因有以下几种:

  • 单子对他们能做什么有限制(详见定义)。
  • 这些限制,加上涉及三个运算的事实,符合范畴论中被称为单子的东西的结构,这是数学中一个模糊的分支。
  • 它们是由"纯"功能语言的支持者设计的。
  • 纯函数语言的拥护者,如数学的模糊分支
  • 因为数学是模糊的,monad与特定的编程风格相关,人们倾向于使用monad这个词作为一种秘密的握手。正因为如此,没有人费心投资一个更好的名字。

  • monad用于控制数据流的抽象数据类型。

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

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

    因此,monad只是一个控制流结构。替换monad的更好的短语是"control type"。

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

    例如,"if"monad:

    1
    if( clause ) then block

    最简单的有两个槽——一个子句和一个块。ifmonad通常用于评估子句的结果,如果不是false,则评估块。许多开发人员在学习"if"时并没有被介绍给monad,而只是不需要理解monad来编写有效的逻辑。

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

    当然,数据结构的迭代或遍历方法与单体的计算方法相同。

    编译器可能支持或不支持用户定义的monad。哈斯克尔当然会。虽然monad这个词在语言中没有使用,但ioke有一些类似的功能。


    我最喜欢的Monad教程:

    http://www.haskell.org/haskellwiki/all-about-monads

    (在谷歌搜索"Monad教程"的17万次点击中!)

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

    Haskell已经拥有了操作符重载(1):它使用的类型类型与Java或C语言中使用接口的方式非常相似,但是Haskell恰好也允许使用非字母数字令牌,例如+&AMP和>作为中缀标识符。如果您的意思是"重载分号"[2],那么它只是一个重载操作符。这听起来像是一种黑色的魔法,它要求麻烦"过载分号"(图片中有进取心的Perl黑客了解这个想法),但关键是没有monad就没有分号,因为纯粹的函数代码不需要或允许显式排序。

    这听起来比需要的要复杂得多。Sigfpe的文章很酷,但是用haskell来解释它,这类文章没有打破理解haskell去呻吟monads和理解monads去呻吟haskell的鸡和蛋的问题。

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

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


    最近我对单子的看法不同。我一直认为它们是以数学的方式抽象出执行顺序,这使得新的多态性成为可能。

    如果您使用的是命令式语言,并且您按顺序编写了一些表达式,那么代码总是按该顺序运行。

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

    因为monad是多态的,所以可以在不同的monad中运行相同的代码,这取决于您的需要。

    另外,在某些情况下,可以将monad与monad变压器结合起来,同时获得多个功能。


    我对单子还是很陌生,但我想我会分享一个我发现非常适合阅读的链接(带图片)。!)http://www.matusiak.eu/numerodix/blog/2012/3/11/monads-for-the-layman/(无隶属关系)

    基本上,我从本文中得到的温暖和模糊的概念是monad基本上是适配器的概念,它允许不同的函数以可组合的方式工作,即能够将多个函数串起来并混合和匹配它们,而不必担心返回类型不一致等等。因此,bind函数负责在我们试图制造这些适配器时,将苹果与苹果、桔子与桔子放在一起。提升功能负责取"低级"功能并"升级",与绑定功能一起工作,也可组合。

    我希望我做的对,更重要的是,希望这篇文章对monads有一个有效的观点。如果没有别的,这篇文章有助于激发我学习更多关于monads的兴趣。


    除了上面的优秀答案之外,我还为您提供以下文章(Patrick Thomson)的链接,该文章通过将概念与JavaScript库jQuery(及其使用"方法链接"来操作DOM的方法)相关联来解释Monads:jquery是monad

    jquery文档本身并不引用术语"monad",而是讨论可能更熟悉的"构建器模式"。这并不能改变这样一个事实,那就是你有一个合适的单子,也许你甚至没有意识到。


    正如丹尼尔·斯皮瓦克解释的那样,单子并不是隐喻,而是一种从普通模式中产生的实际有用的抽象。


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


    monad是将共享公共上下文的计算组合在一起的一种方法。这就像是建立一个管道网络。在构建网络时,没有数据流过它。但当我将所有的位与"bind"和"return"混合在一起之后,我调用了类似于runMyMonad monad data的东西,数据通过管道流动。


    这个答案以一个激励性的例子开始,通过这个例子工作,派生出一个monad的例子,并正式定义"monad"。

    考虑这三个伪代码函数:

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

    f采用形式的有序对,并返回有序对。它使第一个项目保持原样,并将"called f."附加到第二个项目。同g

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

    1
    2
    3
    4
      f(g(wrap(x)))
    = f(g(<x,"">))
    = f(<x,"called g.">)
    = <x,"called g. called f.">

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

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

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

    但是看看当你创作它们时会发生什么:

    1
    2
    3
    4
      f(g(wrap(x)))
    = f(g(<x,"">))
    = f(<<x,"">,"called g.">)
    = <<<x,"">,"called g.">,"called f.">

    问题是,将一对传递给函数并不能满足您的需要。但是,如果你能将一对输入到一个函数中呢:

    1
    2
    3
    4
      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送入f中,把f中取出,返回

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

    注意,当你对你的函数做三件事时会发生什么:

    首先:如果包装一个值,然后将结果对送入函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
      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中:

    1
    2
    3
    4
    5
    6
    7
      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中:

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

    给它喂一对:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
      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是什么类型的值。如果xt型,那么您的对就是"t和string"型的值。称之为M t型。

    EDOCX1 9是一种类型的构造函数:EDCOX1,9,不单独引用一个类型,但是EDCOX1×32 }是用一个类型填充空白之后的一个类型。M int是一对int和一个字符串。M string是一对字符串和一个字符串。等。

    恭喜你,你创造了一个单子!

    正式来说,你的单子是tuple

    monad是一个tuple ,其中:

      百万千克1m是一个类型构造函数。百万千克1百万千克1feed接受(接受t并返回M u的函数)和M t并返回M u的函数。百万千克1百万千克1wrap接受v并返回M v。百万千克1

    tuv是可能相同或可能不同的三种类型。monad满足您为特定monad证明的三个属性:

      百万千克1

      将包装好的t送入函数与将未包装的t传入函数相同。

      正式名称:feed(f, wrap(x)) = f(x)

      百万千克1百万千克1

      M t喂入wrapM t没有任何作用。

      正式名称:feed(wrap, m) = m

      百万千克1百万千克1

      把一个M t(称为m)送入一个函数

        百万千克1将t传递到g中百万千克1百万千克1从g获取M u(称为n)百万千克1百万千克1将n送入f。百万千克1

        百万千克1将m送入g中百万千克1百万千克1从g获取n。百万千克1百万千克1将n送入f。百万千克1

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

      百万千克1

    一般情况下,feed称为bind(哈斯克尔称为>>=),wrap称为return


    在scala的上下文中,您会发现以下是最简单的定义。基本上,平面图(或绑定)是"关联的",并且存在一个标识。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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
      }
    }

    例如。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 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


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

    值得一提的是,这里有一些教程的链接可以帮助我(不,我仍然不知道Monads是什么)。

    • http://ostele.com/archives/2007/12/overloading-分号
    • http://spbhug.folding-maps.org/wiki/monadsen
    • 网址:http://www.loria.fr/~kow/monads/

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

    而除法接受两个分式,并返回一个分数,在haskell somewhy中将零除法定义为无穷大(恰好是一个分数somewhy)。好的。

    在任何情况下,monad似乎只是一种确保您的操作链以可预测的方式运行的方法,而一个声称是num->num的函数,由另一个用x调用的num->num函数组成,并不表示发射导弹。好的。

    另一方面,如果我们有一个可以发射导弹的功能,我们可以把它与其他也可以发射导弹的功能结合起来,因为我们的意图是明确的——我们想发射导弹——但是它不会因为某些奇怪的原因而尝试打印"你好世界"。好的。

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

    如果我有MAIN,我希望它做一系列动作,我运行程序的原因是产生一种效果——通常是通过IO。因此,我可以将IO操作链接在一起,以便——执行IO,而不执行其他操作。好的。

    如果我尝试做一些不"返回IO"的事情,程序会抱怨链不流动,或者基本上"这与我们正在尝试做的事情有什么关系——IO操作",它似乎会迫使程序员保持他们的思路,而不偏离和考虑发射导弹,同时为S创建算法。或者——不流动。好的。

    基本上,monads似乎是编译器的一个提示,"嘿,你知道这个函数在这里返回一个数字,它实际上并不总是有效的,它有时会产生一个数字,有时什么都没有,记住这一点。"知道了这一点,如果您试图断言一个单态操作,那么单态操作可能充当一个编译时异常,它会说:"嘿,这实际上不是一个数字,这可以是一个数字,但是您不能假定这一点,请做些事情来确保流是可接受的。"这在一定程度上防止了不可预测的程序行为。好的。

    似乎单子不是关于纯粹性,也不是关于控制,而是关于维护一个类别的身份,在这个类别上,所有的行为都是可预测和定义的,或者不编译的。当你被要求做某件事时,你不能什么都不做,如果你被要求什么都不做(可见),你就不能做什么。好的。

    对于monads,我能想到的最大原因是——看一下过程/oop代码,你会发现你不知道程序从哪里开始,也不知道程序从哪里结束,你所看到的只是大量的跳跃和大量的数学、魔术和导弹。您将无法维护它,如果可以的话,您将花费大量时间围绕整个程序进行思考,然后才能理解其中的任何部分,因为在此上下文中的模块化是基于代码的跨部门"部分",在这些部分中,代码被优化为尽可能相关,以保证效率/相互关系。.monads是非常具体的,并且通过定义进行了很好的定义,并确保程序流可以进行分析,并隔离难以分析的部分,因为它们本身就是monads。一个单子似乎是一个"可理解的单位,完全理解后是可以预测的"--如果你理解"可能"的单子,除了"可能"之外,它是不可能做任何事情的,它看起来微不足道,但在大多数非单子代码中,一个简单的功能"地狱世界"可以发射导弹,什么都不做,或者摧毁宇宙,甚至是远离宇宙。或者时间——我们既不知道也不保证它是什么。Monad保证它就是它。这是非常强大的。好的。

    "现实世界"中的所有事物似乎都是单子,从某种意义上说,它受到防止混淆的明确可观察的规律的约束。这并不意味着我们必须模仿这个对象的所有操作来创建类,相反,我们可以简单地说"一个正方形就是一个正方形",除了一个正方形,甚至不是一个矩形或一个圆,而且"一个正方形的面积等于它的一个现有维度的长度乘以它自己"。不管你有什么样的正方形,如果它是二维空间中的正方形,那么它的面积绝对不能是任何东西,除了它的长度平方,证明它几乎是微不足道的。这是非常强大的,因为我们不需要做出断言来确保我们的世界是这样的,我们只是利用现实的含义来防止我们的程序偏离轨道。好的。

    我几乎肯定是错的,但我认为这可以帮助别人,所以希望它能帮助别人。好的。好啊。


    这两件事对我的学习起到了最大的帮助:

    第8章,"函数解析器",来自GrahamHutton在Haskell的书编程。实际上,这根本没有提到monad,但是如果您能够通读本章并真正理解其中的所有内容,特别是如何评估一系列绑定操作,那么您将了解monad的内部。希望这需要多次尝试。

    教程都是关于Monads的。这给出了几个很好的例子来说明它们的用法,我不得不说,在Appendex中,这个类比是我为自己工作的。


    我将试着在Haskell的背景下解释Monad

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

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

    我们可以做(f . g) x,这和f (g x)是一样的,其中xInt值。

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

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

    假设我们现在有g1 :: Int -> Maybe Stringf1 :: String -> Maybe Boolg1f1分别与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值从"可能不存在"上下文中去掉,并将其应用于f1中。f1Maybe Bool的结果,是整个>>=操作的结果。

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

    综上所述,Monad类型类定义了>>=运算符,它允许我们将上下文中的值(我们称为这些单值)传递给不期望上下文中的值的函数。上下文将得到处理。

    如果这里有一件事需要记住,那就是Monad允许在上下文中包含值的函数组合。


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


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

    • 单子是分形的

    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.


    博士

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {-# 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

    序言

    应用算子函数

    okay.ZZU1

    典型定义

    okay.

    1
    2
    3
    4
    ($) :: (a -> b) -> a -> b
    f $ x = f x

    infixr 0 $

    在哈斯克尔原始函数的应用中在$术语中定义的.组成

    okay.

    1
    2
    3
    4
    (.) :: (b -> c) -> (a -> b) -> (a -> c)
    f . g = \ x -> f $ g x

    infixr 9 .

    并满足forall f g h.

    okay.

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

    是结合性的,而id是它的权利和左侧的身份。

    okay.克莱斯利三部曲

    在编程中,函子型结构体是一个实例的模子型类。定义与实现有几种等效变量,每一种对Monad抽象的直觉略有不同。

    okay.

    一种函子是一种类型的结构体f,一种类型的* -> *的函子类型。

    okay.

    1
    2
    3
    4
    {-# LANGUAGE KindSignatures #-}

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

    另外,跟踪静态执行型协议,函子类型类的实例必须服从代数函子法EDOCX1〕〔10〕

    okay.

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

    函子计算有类型

    okay.

    1
    forall f t. Functor f => f t

    c的背景下,由r的结果组成。

    okay.

    一种莫纳迪克函数或克莱斯利箭头有这种类型

    okay.

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

    Kleisi arrows are functions that take one regulation aand return a monadic computation m b.

    okay.

    莫纳德是克莱斯利三重埃多克斯1&16术语的典型定义。

    okay.

    1
    (m, return, (=<<))

    作为类型类别实现

    okay.

    1
    2
    3
    4
    5
    class Functor m => Monad m where
       return :: t -> m t
       (=<<)  :: (a -> m b) -> m a -> m b

    infixr 1 =<<

    克莱斯利人的身份是一种克莱斯利箭,它促进了在摩纳哥的环境中的EDOCX1价值。应用克莱斯利箭头a -> m b来计算m a

    okay.

    克莱斯利成分EDOCX1&23

    okay.

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

    infixr 1 <=<

    复合两个Kleisli箭头,应用左箭头得出右箭头的应用结果。

    okay.

    Monad种类的实例必须遵循Monad Laws,在Kleisli组成术语中最优雅的说明:EDOCX1&5)

    okay.

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

    是结合性的,而EDOCX1[…]是它的权利和左侧的身份。

    okay.身份

    身份类型

    okay.

    1
    type Id t = t

    类型上的身份功能

    okay.

    1
    Id :: * -> *

    作为函子解释

    okay.

    1
    2
    3
    4
    5
    6
    7
    8
       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)

    在Canonical Haskell,The identity monad is defined

    okay.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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

    选项

    选择类型

    okay.

    1
    data Maybe t = Nothing | Just t

    计算可能不产生t的结果。选择货币

    okay.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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

    只有当Maybe a产生结果时才适用。

    okay.

    1
    newtype Nat = Nat Int

    自然数可以被编码为这些积分大于或等于零。

    okay.

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

    自然数未在亚牵引下关闭。

    okay.

    1
    2
    3
    4
    (-?) :: Nat -> Nat -> Maybe Nat
    (Nat n) -? (Nat m) = toNat (n - m)

    infixl 6 -?

    选项Monad covers a basic form of exception handling.

    okay.

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

    列表

    列表货币,在列表类型上

    okay.

    1
    2
    3
    data [] t = [] | t : [t]

    infixr 5 :

    及其加性单倍体运算"append"好的。

    1
    2
    3
    4
    5
    (++) :: [t] -> [t] -> [t]
    (x : xs) ++ ys = x : xs ++ ys
    []       ++ ys = ys

    infixr 5 ++

    对非线性计算[t]进行编码,得到结果t的自然量0, 1, ...。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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
       _ =<< []       = []

    扩展将++所有结果列表[b]从Kleisli箭头a -> [b]的应用程序f x连接到[a]的元素,连接到单个结果列表[b]。好的。

    设正整数n的适当除数为好的。

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

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

    然后好的。

    1
    forall n.  let f = f <=< divisors in f n  =  []

    在定义monad类型类时,haskell标准使用其flip(绑定操作符EDOCX1)(11),而不是扩展=<<。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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

       fail :: String -> m a
       fail s = errorWithoutStackTrace s

    为了简单起见,此解释使用类型类层次结构好的。

    1
    2
    class              Functor f
    class Functor m => Monad m

    在haskell中,当前的标准层次结构是好的。

    1
    2
    3
    class                  Functor f
    class Functor p     => Applicative p
    class Applicative m => Monad m

    因为不仅每个单子都是一个函数,而且每个应用程序都是一个函数,每个单子也都是一个应用程序。好的。

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

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

    大致翻译为do块好的。

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

    等效单子理解好的。

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

    以及表达好的。

    1
    2
    3
    4
    5
    6
    7
    [1 .. 10] >>= (\ a ->
       [1 .. 10] >>= (\ b ->
          let p = a * b in
             guard (even p) >>
                return p
       )
    )

    对于嵌套的绑定表达式来说,do符号和monad理解是句法上的糖分。bind运算符用于一元结果的本地名称绑定。好的。

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

    哪里好的。

    1
    2
    3
    4
    (&) :: a -> (a -> b) -> b
    (&) = flip ($)

    infixl 0 &

    定义了保护功能好的。

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

    其中单位类型或"空元组"好的。

    1
    data () = ()

    支持选择和失败的加法单元可以通过使用类型类抽象出来。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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 k l m.。好的。

    1
    2
    3
         fail <|> l  =  l
         k <|> fail  =  k
    (k <|> l) <|> m  =  k <|> (l <|> m)

    fail是加性单体的吸收/湮灭零元素。好的。

    1
    _ =<< fail  =  fail

    如果在好的。

    1
    guard (even p) >> return p

    even p为真,则防护产生[()],根据>>的定义,局部常数函数好的。

    1
    \ _ -> return p

    应用于结果()。如果为假,则警卫会生成Monad的fail[],这不会导致将kleisli箭头应用于>>。好的。状态

    众所周知,monad用于对有状态计算进行编码。好的。

    状态处理器是一个函数好的。

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

    这将使状态st转换,并产生结果t。国家可以是任何东西。无,标记,计数,数组,句柄,机器,世界。好的。

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

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

    状态处理器monad是同类的* -> *函数或State st函数。状态处理器monad的kleisli箭头是函数好的。

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

    在canonical haskell中,定义了状态处理器monad的懒惰版本。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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

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

    1
    2
    3
    4
    5
    6
    7
    8
    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

    状态访问由原语getput提供,状态单子上的抽象方法:好的。

    1
    2
    3
    4
    5
    {-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

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

    m -> st建议功能的状态依赖型st在单子mState t认为,例如,将确定的状态是t独特型。

    1
    2
    3
    4
    5
    6
    instance Stateful (State st) st where
       get :: State st st
       get = State $ \ s -> (s, s)

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

    与使用单位对在C型analogously void

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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是一场accessors与记录。

    等效可变状态的线程单子

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

    s0 :: Int同样可靠,是透明的,但更多的优雅和实用infinitely

    1
    2
    3
    4
    5
    6
    7
    (flip run) 34
       (do
          modify (+ 1)
          n <- gets (* 12)
          modify (+ 7)
          return (show n)
       )

    modify (+ 1)是计算State Int ()型,除了其对return ()效应等效。

    1
    2
    3
    4
    5
    6
    7
    (flip run) 34
       (modify (+ 1) >>
          gets (* 12) >>= (\ n ->
             modify (+ 7) >>
                return (show n)
          )
       )

    该法associativity单子可以写在条款的forall m f g.>>=

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

    1
    2
    3
    4
    5
    6
    7
    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                 }
    }                    }

    中的表达状导向编程(如锈),最后一块是其产量的声明。该绑定是有时称为"可编程逻辑算子的分号"。

    primitives迭代控制结构是从一emulated monadically结构化编程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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

    输入/输出

    1
    data World

    《世界的状态处理器的I / O是一个和解的Haskell单子纯粹和真实的世界,一denotative和操作语义的功能。封闭当前模拟的严格执行:

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

    的相互作用是通过提高primitives不纯

    1
    2
    3
    4
    5
    6
    7
    getChar         :: IO Char
    putChar         :: Char -> IO ()
    readFile        :: FilePath -> IO String
    writeFile       :: FilePath -> String -> IO ()
    hSetBuffering   :: Handle -> BufferMode -> IO ()
    hTell           :: Handle -> IO Integer
    . . .              . . .

    该代码是用IO杂质是一个永久的primitives按照治疗方案的类型系统。因为性是可怕的,什么发生在IO,stays IO中。

    1
    unsafePerformIO :: IO t -> t

    或者,至少,应该的。

    Haskell程序的类型的签名)。

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

    扩大到

    1
    World -> ((), World)

    该功能是变换的世界。

    epilogue

    在Haskell的类型和对象类是送来的Haskell函数之间的态射是送来的类型是"快速和宽松"的范畴,Hask

    t函子的范畴是从C映射到类中的每个对象的CD;对象在一D

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

    在与每个在其morphism Cmorphism D

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

    XYC对象中。HomC(X, Y)是同态的态射类中的所有X -> YC。的身份和morphism函子保持必须的成分,"结构"中的CD

    1
    2
    3
    4
                        Tmor    Tobj

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

    本研究kleisli类类C给出由三kleisli

    1
    <T, eta, _*>

    一endofunctor

    1
    T : C -> C

    (一fmorphism eta(身份),return),和一个扩展*(=<<)算子。

    在每个kleisli morphism Hask

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

    由扩展算子

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

    给出了该类中的kleisli morphism Hask

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

    一类组合在kleisli .T给出的扩展条款

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

    在axioms和satisfies类

    1
    2
    3
    4
    5
    6
    7
    8
           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

    其中,应用等价转换好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
         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

    在扩展方面,是规范的给予。好的。

    1
    2
    3
    4
    5
    6
    7
    8
                   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也可以定义为自然转换mu,而不是kleislian扩展,在名为join的编程中。单胞菌在mu的术语中被定义为内函数类C的三倍。好的。

    1
    2
         T :  C -> C
         f :: * -> *

    两种自然变形好的。

    1
    2
    3
    4
    5
       eta :  Id -> T
    return :: t  -> f t

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

    满足同等条件好的。

    1
    2
    3
    4
    5
           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类型类好的。

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

    期权Monad的规范mu实施:好的。

    1
    2
    3
    4
    5
    instance Monad Maybe where
       return = Just

       join (Just m) = m
       join Nothing  = Nothing

    concat函数好的。

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

    是单子列表中的join。好的。

    1
    2
    3
    4
    5
    6
    instance Monad [] where
       return :: t -> [t]
       return = (: [])

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

    join的实现可以使用等价物从扩展形式转换而来。好的。

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

    mu到扩展形式的反向转换由下式给出:好的。

    1
    2
         f*  =  mu . T(f)     :  T(X) -> T(Y)
    (f =<<)  =  join . map f  :: m a -> m b
    • PhilipWadler:函数编程的Monads好的。

    • Simon L Peyton Jones,Philip Wadler:命令式函数编程好的。

    • Jonathan M.D.Hill,Keith Clarke:分类理论导论,分类理论单子及其与函数编程的关系’好的。

    • 克莱斯利分类好的。

    • 优生学:计算和单子的概念好的。

    • 单子不是什么好的。

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

    Ok.

    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.

    Ok.

    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!

    Ok.

    从将单子泛化到约翰·休斯的《箭》。好的。好啊。


    monad是用来封装具有变化状态的对象的东西。它通常出现在不允许您具有可修改状态的语言中(例如,haskell)。

    文件I/O就是一个例子。

    您可以使用monad作为文件I/O,将不断变化的状态性质隔离为只使用monad的代码。monad中的代码可以有效地忽略monad之外世界的变化状态——这使得理解程序的整体效果变得容易得多。


    公主对f计算表达式的解释帮助了我,尽管我仍然不能说我真的理解。

    编辑:这个系列-用javascript解释Monads-是一个"平衡"我。

      百万千克1

      http://blog.jcoglan.com/2011/03/05/translation-from-haskell-to-javascript-of-selected-portions-of-the-best-introduction-to-monads-ive-ever-read(从-haskell-to-javascript-of-selected-portions-of-the-best-introdu/

      百万千克1百万千克1

      http://blog.jcoglan.com/2011/03/06/monad-syntax-for-javascript/

      百万千克1百万千克1

      http://blog.jcoglan.com/2011/03/11/promises-are-the-monad-of-asynchronous-programming/

      百万千克1

    我认为理解单子会让你毛骨悚然。从这个意义上说,尽可能多地阅读"教程"是一个好主意,但通常奇怪的东西(不熟悉的语言或语法)会阻止你的大脑集中精力于基本的东西。

    有些事情我很难理解:

      百万千克1基于规则的解释对我来说根本不起作用,因为大多数实际示例实际上需要的不仅仅是返回/绑定。百万千克1百万千克1而且,称他们为规则也没有帮助。它更像是一个例子,"有些东西有共同点,让我们把它们叫做‘monads’,而共同点叫做‘rules’"。百万千克1百万千克1return(a -> M和bind(M -> (a -> M) -> M是很好的,但我不明白bind是如何从M中提取a以进入a -> M的。我不认为我在任何地方读过(可能对其他人来说很明显),返回的反转(M -> a必须存在于monad中,它不需要被暴露。百万千克1


    解释monad似乎就像解释控制流语句。假设一个非程序员要求你解释它们?

    你可以给他们一个涉及理论的解释-布尔逻辑,寄存器值,指针,堆栈和帧。但那太疯狂了。

    你可以用句法来解释它们。基本上,C中的所有控制流语句都有花括号,您可以根据条件和条件代码相对于括号的位置来区分它们。那可能更疯狂。

    或者,您也可以解释循环、if语句、例程、子例程,以及可能的协同例程。

    monads可以取代相当多的编程技术。在支持它们的语言中有一种特定的语法,以及一些关于它们的理论。

    它们也是函数式程序员使用命令式代码而实际上不承认它的一种方式,但这并不是它们唯一的用途。


    我也在试着理解单子。这是我的版本:

    单子是关于重复性事物的抽象。首先,monad本身是一个类型化接口(类似于抽象的泛型类),它有两个函数:具有定义签名的bind和return。然后,我们可以基于抽象的monad创建具体的monad,当然还有bind和return的具体实现。此外,bind和return必须满足一些不变量,以便能够组成/链接具体的monad。

    当我们有接口、类型、类和其他工具来创建抽象时,为什么要创建monad概念?因为monads提供了更多:它们以一种方式强制重新思考问题,使得可以在不使用任何样板文件的情况下合成数据。


    在课程"反应式编程原理"培训中,Erik Meier将其描述为:

    1
    "Monads are return types that guide you through the happy path." -Erik Meijer


    一个非常简单的答案是:

    monad是一种抽象,它为封装值、计算新的封装值和解包封装值提供了接口。

    在实践中,它们的便利之处在于,它们提供了一个统一的接口,用于创建建模状态而非状态的数据类型。

    重要的是要理解monad是一种抽象,即处理某种数据结构的抽象接口。然后,该接口用于构建具有单态行为的数据类型。

    您可以在Ruby的Monads中找到一个非常好和实用的介绍,第1部分:介绍。


    实际上,monad允许回调嵌套(具有相互递归的线程状态(请注意连字符)(以可组合(或可分解)的方式)(具有类型安全性(有时(取决于语言))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))

    例如,这不是单子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //JavaScript is 'Practical'
    var getAllThree =
             bind(getFirst, function(first){  
      return bind(getSecond,function(second){  
      return bind(getThird, function(third){  
        var fancyResult = // And now make do fancy
                          // with first, second,
                          // and third
        return RETURN(fancyResult);
      });});});

    但是monads支持这种代码。monad实际上是一组类型,用于:{bind,RETURN,maybe others I don't know...}。这在本质上是不必要的,实际上是不切实际的。

    现在我可以用它了:

    1
    2
    3
    4
    5
    6
    7
    var fancyResultReferenceOutsideOfMonad =  
      getAllThree(someKindOfInputAcceptableToOurGetFunctionsButProbablyAString);  

    //Ignore this please, throwing away types, yay JavaScript:
    //  RETURN = K
    //  bind = \getterFn,cb ->
    //    \in -> let(result,newState) = getterFn(in) in cb(result)(newState)

    或者打破它:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var getFirstTwo =
               bind(getFirst, function(first){  
        return bind(getSecond,function(second){  
          var fancyResult2 = // And now make do fancy
                             // with first and second
          return RETURN(fancyResult2);
        });})
      , getAllThree =
               bind(getFirstTwo, function(fancyResult2){  
        return bind(getThird,    function(third){  
          var fancyResult3 = // And now make do fancy
                             // with fancyResult2,
                             // and third
          return RETURN(fancyResult3);
        });});

    或忽略某些结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var getFirstTwo =
               bind(getFirst, function(first){  
        return bind(getSecond,function(second){  
          var fancyResult2 = // And now make do fancy
                             // with first and second
          return RETURN(fancyResult2);
        });})
      , getAllThree =
               bind(getFirstTwo, function(____dontCare____NotGonnaUse____){  
        return bind(getThird,    function(three){  
          var fancyResult3 = // And now make do fancy
                             // with `three` only!
          return RETURN(fancyResult3);
        });});

    或者简化一个简单的案例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var getFirstTwo =
               bind(getFirst, function(first){  
        return bind(getSecond,function(second){  
          var fancyResult2 = // And now make do fancy
                             // with first and second
          return RETURN(fancyResult2);
        });})
      , getAllThree =
               bind(getFirstTwo, function(_){  
        return bind(getThird,    function(three){  
          return RETURN(three);
        });});

    至(使用"正确身份"):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var getFirstTwo =
               bind(getFirst, function(first){  
        return bind(getSecond,function(second){  
          var fancyResult2 = // And now make do fancy
                             // with first and second
          return RETURN(fancyResult2);
        });})
      , getAllThree =
               bind(getFirstTwo, function(_){  
        return getThird;
        });

    或者把它们塞回一起:

    1
    2
    3
    4
    5
    var getAllThree =
               bind(getFirst, function(first_dontCareNow){  
        return bind(getSecond,function(second_dontCareNow){  
        return getThird;
        });});

    这些能力的实用性并没有真正显现出来,或者在你试图解决真正棘手的问题之前保持清醒比如解析,或者模块/Ajax/资源加载。

    你能想象成千上万行的indexof/substring逻辑吗?如果频繁的解析步骤包含在小函数中怎么办?像charsspacesupperCharsdigits这样的功能?如果这些函数在回调中给出了结果,不必与regex组和arguments.slice混淆?如果他们的组成/分解被很好地理解呢?这样你就可以自下而上构建大型解析器了?

    所以管理嵌套回调范围的能力是非常实用的,尤其是在使用单语法分析器组合器库时。(也就是说,根据我的经验)

    不要挂断电话:-范畴论- Maybe单子-单子定律-哈斯克尔-!!!!!


    下面是一些"{| a |m}"代表一片monadic日期。一个日期型advertises a

    1
    2
    3
            (I got an a!)
              /        
        {| a |m}

    函数,f,知道如何创建一个单子,如果只有一个:它有a

    1
    2
    3
    4
    5
    6
    7
    8
    9
           (Hi f! What should I be?)
                          /
    (You?. Oh, you'll be /
     that data there.)  /
     /                 /  (I got a b.)
    |    --------------      |
    |  /                     |
    f a                      |
      |--later->       {| b |m}

    在这里,我们看到的功能,探讨其tries f,但有rebuked单子。

    1
    2
    3
    4
    5
    (Hmm, how do I get that a?)
     o       (Get lost buddy.
    o         Wrong type.)
    o       /
    f {| a |m}

    f发现功能的方式,利用>>=a提取物。

    1
    2
    3
    4
    5
    6
    7
            (Muaahaha. How you
             like me now!?)      
        (Better.)      \
            |     (Give me that a.)
    (Fine, well ok.)    |
             \          |
       {| a |m}   >>=   f

    不知道f小的单子,是在与>>=合谋。

    1
    2
    3
    4
    5
    6
    7
    8
                (Yah got an a for me?)      
    (Yeah, but hey    |
     listen. I got    |
     something to     |
     tell you first   |
     ...)   \        /
             |      /
       {| a |m}   >>=   f

    但事实上,他们谈论的是什么?嗯,这取决于上的单子。会说话的蛛网膜下腔出血(SAH)有限公司使用完全抽象的;你必须体验到一些特别的monads -PSP与身上的理解。

    例如,可能的数据类型

    1
     data Maybe a = Nothing | Just a

    有个单子样将以下行为的实例…………………

    其中,如果是Just a案例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
                (Yah what is it?)      
    (... hm? Oh,      |
    forget about it.  |
    Hey a, yr up.)    |
                \     |
    (Evaluation  \    |
    time already? \   |
    Hows my hair?) |  |
          |       /   |
          |  (It's    |
          |  fine.)  /
          |   /     /    
       {| a |m}   >>=   f

    但情况Nothing

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
            (Yah what is it?)      
    (... There      |
    is no a. )      |
      |        (No a?)
    (No a.)         |
      |        (Ok, I'll deal
      |         with this.)
       \            |
        \      (Hey f, get lost.)
         \          |   ( Where's my a?
          \         |     I evaluate a)
           \    (Not any more  |
            \    you don't.    |
             |   We're returning
             |   Nothing.)   /
             |      |       /
             |      |      /
             |      |     /
       {| a |m}   >>=   f      (I got a b.)
                        |  (This is   \
                        |   such a     \
                        |   sham.) o o  \
                        |               o|
                        |--later-> {| b |m}

    也许是我们的计算,如果它继续单子,其实是包含在aaborts advertises,但如果它不是在计算的结果,但是,仍然是一片monadic日期,但不是f产出。也许是这个原因,单子是用来代表当前的失败。

    monads differently表现不同。与其他类型的数据列表是monadic实例。他们的行为如以下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    (Ok, here's your a. Well, its
     a bunch of them, actually.)
      |
      |    (Thanks, no problem. Ok
      |     f, here you go, an a.)
      |       |
      |       |        (Thank's. See
      |       |         you later.)
      |  (Whoa. Hold up f,      |
      |   I got another         |
      |   a for you.)           |
      |       |      (What? No, sorry.
      |       |       Can't do it. I
      |       |       have my hands full
      |       |       with all these"b"
      |       |       I just made.)
      |  (I'll hold those,      |
      |   you take this, and   /
      |   come back for more  /
      |   when you're done   /
      |   and we'll do it   /
      |   again.)          /
       \      |  ( Uhhh. All right.)
        \     |       /    
         \    \      /
    {| a |m}   >>=  f

    在这个案例的功能,知道如何使从它的输入列表,但不知道该怎么做额外的输入和额外的列表。该绑定是由f>>=,出的多输出相结合。这个例子表明,包括一对在>>=负责提取a,它也有可能访问到的产出f束缚。事实上,它将永远不会知道,除非它在任何a提取物可能具有相同的输出类型的上下文。

    有其他monads这是用来代表不同的情况。这是一些characterizations of等等更多。在IO单子是不是真的有一个a,但它知道的人,你将得到a。隐藏的秘密之State st单子有st通,这将对下ff凸轮表,虽然只是一a所要求的。在Reader rState st单子是同类的,虽然它fr只让看。

    这一点在所有的是,任何类型的数据,这是符合其本身是一declaring单子的一些排序)中提取价值从周围的单子。大增益从所有这一切?嗯,它的易用性有足够之沙发排序计算的上下文。它可以把凌乱的,然而,当本拉登在stringing多上下文的计算。在照顾解决单子业务上下文的相互作用(这样的程序员是不是必须的。

    注意,使用的>>=eases)由制造一些混乱的f自主的离开。这是的情况下,在上述的Nothing例如,f不再能决定该怎么做的情况下,在Nothing>>=;它的编码。这是权衡。如果它是必要的适当的f决定该怎么做的情况下,应该有一个fNothing,然后从Maybe aMaybe b函数)。在这个案例,是一Maybe单子被失去。

    注意,然而,有时在没有数据类型的构造函数(口*,看着你,如果你想),我们的工作与我们的小,但有价值的选择上与它的工作monadic接口。


    另一个解释monads的尝试,只使用python列表和map函数。我完全接受这不是一个完整的解释,但我希望它能成为核心概念。

    我从Monads上的funfunfunction视频中得到了这个基础,并且学习了haskell章节"更多的Monads"。我强烈推荐观看FunfunFunction视频。

    最简单的是,monad是具有mapflatMap功能的对象(haskell中的bind)。有一些额外的必需属性,但这些是核心属性。

    flatMap将映射的输出"展平",对于列表,这只是连接列表的值,例如。

    1
    concat([[1], [4], [9]]) = [1, 4, 9]

    所以在python中,我们基本上可以用这两个函数实现monad:

    1
    2
    3
    4
    5
    def flatMap(func, lst):
        return concat(map(func, lst))

    def concat(lst):
        return sum(lst, [])

    func是接受一个值并返回一个列表的任何函数,例如

    1
    lambda x: [x*x]

    解释

    为了清晰起见,我通过一个简单的函数在python中创建了concat函数,它对列表进行了汇总,即[] + [1] + [4] + [9] = [1, 4, 9](haskell有一个本机concat方法)。

    我假设您知道map函数是什么,例如:

    1
    2
    >>> list(map(lambda x: [x*x], [1,2,3]))
    [[1], [4], [9]]

    展平是monad的关键概念,对于每个monad对象,这种展平允许您获得包装在monad中的值。

    现在我们可以打电话给:

    1
    2
    >>> flatMap(lambda x: [x*x], [1,2,3])
    [1, 4, 9]

    此lambda正在获取值x并将其放入列表中。monad可以处理从值到monad类型的任何函数,在本例中是一个列表。

    这就是你的单子定义。

    我认为他们为什么有用的问题已经在其他问题中得到了解答。

    更多解释

    其他没有列出的例子是javascript承诺,它有then方法和javascript流,它们有flatMap方法。

    所以promises和streams使用了一个稍微不同的函数,它将流或promises展平并从内部返回值。

    haskell列表monad具有以下定义:

    1
    2
    3
    4
    instance Monad [] where  
        return x = [x]  
        xs >>= f = concat (map f xs)  
        fail _ = []

    即:return有三个函数(在大多数其他语言中不与返回混淆)、>>=fail

    希望您能看到以下两者的相似之处:

    1
    xs >>= f = concat (map f xs)

    还有:

    1
    2
    def flatMap(f, xs):
        return concat(map(f, xs))

    http://mikehadlow.blogspot.com/2011/02/monads-in-c-8-video-of-my-ddd9-monad.html

    这是你要找的视频。

    在C中演示组成和对齐类型的问题,然后在C中正确地实现它们。最后,他展示了相同的C代码在F中的外观,最后在Haskell中的外观。


    Monad是一个Applicative(即可以提升二进制的东西,因此,"any ary"—函数到(1)并向(2)中注入纯值)Functor(即可以映射的东西,(3)或提升一元函数到(3)),增加了扁平嵌套数据类型的能力。在Haskell中,这种扁平化操作称为join

    对于列表,特定类型为:

    1
    join  ::  [[a]] -> [a]

    适用于任何monad的一般类型是:

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

    bind(>>=操作符)只是fmapjoin的组合,即(m >>= f) = join (fmap f m)

    对于许多单子,do符号do { x <- m; return x }和bind运算符m >>= (\x -> return x)都可以读作

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

    (1)与liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c一起

    (2)与pure :: Applicative f => a -> f a一起

    (3)带fmap :: Functor f => (a -> b) -> f a -> f b

    还有等效的单子方法,

    1
    2
    3
    liftM2 :: Monad f => (a -> b -> c) -> f a -> f b -> f c
    return :: Monad f => a -> f a
    liftM  :: Monad f => (a -> b) -> f a -> f b

    数学思维

    简而言之:用于组合计算的代数结构。

    return data:创建一个只在monad世界中生成数据的计算。

    (return data) >>= (return func):第二个参数接受第一个参数作为数据生成器,并创建连接它们的新计算。

    您可以认为(>>=)和返回本身不会进行任何计算。它们只是简单地组合和创建计算。

    任何单元计算都将在且仅当主函数触发时进行计算。


    如果你要求对如此抽象的东西做一个简洁、实用的解释,那么你只能希望得到一个抽象的答案:

    1
    a -> b

    是表示从as到bs的计算的一种方法。您可以将计算链接起来,也就是将它们组合在一起:

    1
    (b -> c) -> (a -> b) -> (a -> c)

    更复杂的计算需要更复杂的类型,例如:

    1
    a -> f b

    是从as到bs的计算类型,这些计算是进入fs的。您还可以组合它们:

    1
    (b -> f c) -> (a -> f b) -> (a -> f c)

    事实证明,这种模式在任何地方都会出现,并且与上面的第一个组合(关联性、右和左标识)具有相同的属性。

    我们必须给这个模式起一个名字,但是如果知道第一个构图的形式特征是一个半群体,这会有帮助吗?

    "单子和括号一样有趣和重要"(奥列格基塞利约夫)


    monad是一个带有特殊机器的盒子,它允许你用两个嵌套的盒子制作一个普通的盒子,但是仍然保留了两个盒子的一些形状。好的。

    具体来说,它允许您执行Monad m => m (m a) -> m a类型的join。好的。

    它还需要一个return操作,它只是包装一个值。return :: Monad m => a -> m a你也可以说join拆箱和return包装——但是join不是Monad m => m a -> a型(它不拆开所有的单体,而是拆开里面有单体的单体。)好的。

    因此,它需要一个单体盒子(Monad m =>m,里面有一个盒子((m a)),并制作一个普通的盒子(m a)。好的。

    然而,通常用monad来表示(>>=)(口语"bind")运算符,它基本上只是fmapjoin相继出现。具体地说,好的。

    1
    2
    x >>= f = join (fmap f x)
    (>>=) :: Monad m => (a -> m b) -> m a -> m b

    注意,该函数出现在第二个参数中,与fmap相反。好的。

    还有,join = (>>= id)。好的。

    为什么这样有用?本质上,它允许您在某种框架(monad)中工作时,生成将动作串在一起的程序。好的。

    单胞菌在哈斯克尔最显著的用途是单胞菌IO。现在,IO是在haskell中对动作进行分类的类型。在这里,monad系统是唯一的保存方法(大花哨的词):好的。

    • 参考透明度
    • 懒惰
    • 纯度

    本质上,一个IO操作(如getLine :: IO String)不能用字符串替换,因为它总是有不同的类型。把IO想象成一个神奇的盒子,它能把东西传送给你。但是,仍然只是说getLine :: IO String和所有函数接受IO a会导致混乱,因为可能不需要这些函数。const"üp§" getLine会做什么?(const放弃了第二个论点。const a b = a不需要评估getLine,但应该做IO!这使得行为变得相当不可预测——也使得类型系统不那么"纯粹",因为所有函数都采用aIO a值。好的。

    输入IOmonad。好的。

    要将操作串在一起,只需扁平嵌套的操作。为了将函数应用于IO操作的输出,在IO a类型中的a只需使用(>>=)即可。好的。

    例如,输出输入行(输出行是产生IO动作的函数,与EDOCX1的右参数(33)匹配):好的。

    1
    2
    getLine >>= putStrLn :: IO ()
    -- putStrLn :: String -> IO ()

    这可以用do环境更直观地写:好的。

    1
    2
    do line <- getLine
       putStrLn line

    本质上,一个do区块如下:好的。

    1
    2
    3
    4
    5
    6
    7
    do x <- a
       y <- b
       z <- f x y
       w <- g z
       h x
       k <- h z
       l k w

    …转化为:好的。

    1
    2
    3
    4
    5
    6
    7
    a     >>= \x ->
    b     >>= \y ->
    f x y >>= \z ->
    g z   >>= \w ->
    h x   >>= \_ ->
    h z   >>= \k ->
    l k w

    还有m >>= \_ -> f>>操作符(当框中的值不需要在框中生成新框时)也可以写a >> b = a >>= const b(const a b = a)好的。型

    另外,return运算符是根据IO直观性建模的-它返回一个具有最小上下文的值,在这种情况下没有IO。由于IO a中的a表示返回的类型,这与命令式编程语言中的return(a)类似,但它不会阻止动作链!f >>= return >>= gf >>= g相同。只有当您返回的术语在链的早期创建时,它才有用——见上文。好的。型

    当然,还有其他的单子,否则它不会被称为单子,它会被称为"IO控制"之类的东西。好的。型

    例如,列表monad(Monad []通过连接变平—使(>>=)运算符对列表的所有元素执行函数。这可以看作是"不确定性",其中列表是许多可能的值,monad框架正在进行所有可能的组合。好的。型

    例如(在GHCI中):好的。型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Prelude> [1, 2, 3] >>= replicate 3  -- Simple binding
    [1, 1, 1, 2, 2, 2, 3, 3, 3]
    Prelude> concat (map (replicate 3) [1, 2, 3])  -- Same operation, more explicit
    [1, 1, 1, 2, 2, 2, 3, 3, 3]
    Prelude> [1, 2, 3] >>"uq"
    "uququq"
    Prelude> return 2 :: [Int]
    [2]
    Prelude> join [[1, 2], [3, 4]]
    [1, 2, 3, 4]

    因为:好的。型

    1
    2
    3
    join a = concat a
    a >>= f = join (fmap f a)
    return a = [a]  -- or"= (:[])"

    "也许单子"只会使Nothing的所有结果无效。也就是说,绑定自动检查函数(a >>=f是否返回或值(a>>= f是否为Nothing—然后返回Nothing。好的。型

    1
    2
    3
    4
    join       Nothing  = Nothing
    join (Just Nothing) = Nothing
    join (Just x)       = x
    a >>= f             = join (fmap f a)

    或者更明确地说:好的。型

    1
    2
    Nothing  >>= _      = Nothing
    (Just x) >>= f      = f x

    状态monad用于修改某些共享状态的函数s -> (a, s),因此>>=的参数是:: a -> s -> (a, s)。这个名称有点用词不当,因为State实际上是用于状态修改函数,而不是用于状态—状态本身确实没有有趣的属性,它只是被更改了。好的。型

    例如:好的。型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    pop ::       [a] -> (a , [a])
    pop (h:t) = (h, t)
    sPop = state pop   -- The module for State exports no State constructor,
                       -- only a state function

    push :: a -> [a] -> ((), [a])
    push x l  = ((), x : l)
    sPush = state push

    swap = do a <- sPop
              b <- sPop
              sPush a
              sPush b

    get2 = do a <- sPop
              b <- sPop
              return (a, b)

    getswapped = do swap
                    get2

    然后:好的。型

    1
    2
    3
    4
    5
    6
    7
    8
    Main*> runState swap [1, 2, 3]
    ((), [2, 1, 3])
    Main*> runState get2 [1, 2, 3]
    ((1, 2), [1, 2, 3]
    Main*> runState (swap >> get2) [1, 2, 3]
    ((2, 1), [2, 1, 3])
    Main*> runState getswapped [1, 2, 3]
    ((2, 1), [2, 1, 3])

    也:好的。型

    1
    2
    Prelude> runState (return 0) 1
    (0, 1)

    。好啊。


    解释

    这是非常简单的,当用C/JAVA术语解释时:

  • monad是一个接受参数并返回特殊类型的函数。

  • 这个monad返回的特殊类型也称为monad。(单子是1和2的组合)

  • 有一些语法上的甜头可以使调用这个函数和类型转换更容易。

  • 例子

    monad对于使函数式编程人员的生活更简单很有用。典型的例子:Maybemonad有两个参数,一个值和一个函数。如果传递值为null,则返回null。否则,它将评估函数。如果我们需要一个特殊的返回类型,我们也会称这个返回类型为Maybe。一个非常粗略的实现方式如下:

    1
    2
    3
    4
    5
    6
    7
    object Maybe(object value, Func<object,object> function)
    {
        if(value==null)
            return null;

        return function(value);
    }

    这在C语言中是非常无用的,因为这种语言缺少使monad有用所需的语法糖。但是monad允许您用函数式编程语言编写更简洁的代码。

    程序员经常用链子来调用monad,就像这样:

    1
    var x = Maybe(x, x2 => Maybe(y, y2 => Add(x2, y2)));

    在本例中,只有当xy都是非null时,才会调用Add方法,否则将返回null

    回答

    回答最初的问题:monad是一个函数和一个类型。就像一个特殊的interface的实现。


    遵循你简短、简洁、实用的指示:

    理解monad最简单的方法是在上下文中应用/组合函数。假设您有两个计算,都可以看作是两个数学函数fg

    • f取一个字符串并产生另一个字符串(取前两个字母)
    • g接受一个字符串并生成另一个字符串(大写转换)

    因此,在任何语言中,"取前两个字母并将其转换为大写字母"的转换都将被写入g(f("some string")。所以,在纯完美函数的世界里,构图就是:先做一件事,然后再做另一件事。

    但假设我们生活在一个功能失效的世界里。例如:输入字符串可能有一个字符长,所以f会失败。所以在这种情况下

    • f接受一个字符串并生成一个字符串或不生成任何字符串。
    • 只有在f没有失败的情况下,g才会生成字符串。否则,什么都不会产生

    所以现在,g(f("some string")需要一些额外的检查:"compute f,如果失败,那么g应该什么也不返回,否则compute g"

    这个想法可以应用于任何参数化类型,如下所示:

    让上下文[someType]是上下文中某个类型的计算。考虑功能

    • f:: AnyType -> Context[Sometype]
    • g:: Sometype -> Context[AnyOtherType]

    组合g(f())应读作"compute f.在此上下文中进行一些额外计算,然后在上下文中有意义的情况下计算g"