什么是monad? [英] What is a monad?

查看:99
本文介绍了什么是monad?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在最近简单介绍了Haskell之后,简单,简洁,实用的解释是什么monad实质上是什么?



我发现我所遇到的大部分解释是相当难以获得的,并且缺乏实际的细节。 解决方案

如果你不是数学家,monad 有点空虚。另一个术语是计算构建器,它对它们实际上有用的内容进行了更详细的描述。



你问一些实际的例子: p>

示例1:列表理解

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

这个表达式返回所有奇数的双数数字在1到10之间。非常有用!

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

  do 
x < - [1..10]
如果单数x
则[x * 2]
else []

甚至:

pre $ [1..10]>> =(\ x - >如果奇数x则[ x * 2] else [])

示例2:输入/输出

  do 
putStrLn你叫什么名字?
name< - getLine
putStrLn(Welcome,++ name ++!)

这两个示例都使用monads,AKA计算构建器。共同的主题是monad 链接操作以某种特定的,有用的方式。在列表理解中,操作是链接的,如果一个操作返回一个列表,那么在列表中的每个项目上执行以下操作。另一方面,IO monad依次执行操作,但是传递一个隐藏变量,它代表世界的状态,它允许我们以纯功能的方式编写I / O代码。



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



另一个例子是异常:使用 Error monad,操作链接在一起,以便它们按顺序执行,除非抛出错误,在这种情况下,其余的。

列表理解语法和do-notation都是使用>>链接操作的语法糖。 = 运算符。 monad基本上只是一个支持>> = 运算符的类型。



:一个解析器



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

  parseExpr = parseString< |> parseNumber 
$ b parseString = do
char'''
x< - many(noneOf\)
char'''
return( StringValue x)

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

操作 char digit 等等非常简单,它们匹配或不匹配,魔术是管理控制流的monad:这些操作是按顺序执行的,直到匹配失败,在这种情况下monad回溯到最新的<



示例4 :异步编程

上面的例子在Haskell中,但是事实证明 F#也支持单子。这个例子被盗了fr om Don Syme

  let AsyncHttp(url:string)= 
async {let req = WebRequest.Create(url)
让! rsp = req.GetResponseAsync()
使用stream = rsp.GetResponseStream()
使用reader = new System.IO.StreamReader(stream)
return reader.ReadToEnd()}

此方法提取网页。妙语是使用 GetResponseAsync - 它实际上等待在单独线程上的响应,而主线程从函数返回。最后三行在接收到响应时在产生的线程上执行。

在大多数其他语言中,您必须为处理响应的行显式创建单独的函数。 async monad可以自行分割块并推迟后半部分的执行。 ( async {} 语法指示块中的控制流由 async monad定义)如何工作

那么monad如何完成所有这些奇特的控制流程事情?实际发生在do-block(或者是在F#中调用的计算表达式)中的实际情况是每个操作(基本上每行)都包含在一个单独的匿名函数中。然后使用 bind 操作符(在Haskell中拼写为>> = )将这些函数组合在一起。由于绑定操作将函数组合在一起,因此它可以按照它认为合适的方式执行它们:顺序地多次,反过来放弃一些,在感觉像一个单独的线程时执行一些它等等。



举例来说,这是示例2的IO代码的扩展版本:

  putStrLn你叫什么名字? 
>> =(\_ - > getLine)
>> =(\名称 - > putStrLn(Welcome,++ name ++!))

这很丑陋,但实际发生的情况也更为明显。 >> = 运算符是神奇的成分:它取一个值(在左边)并将它与一个函数结合(在右边)一个新的价值。然后这个新的值被下一个>> = 操作符所采用,并再次与一个函数结合以产生一个新值。 >> = 可以被视为一个迷你评估者。 $ b

>> =< / code>因为不同的类型被重载,所以每个monad都有自己的>> = 的实现。 (链中的所有操作都必须是相同monad的类型,否则>> = 操作符将不起作用。)



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



在一个操作中传递值到另一个操作值有一些额外的聪明之处,但这需要对Haskell类型系统进行更深入的解释。 b
$ b

总结

在Haskell中,monad是一个参数化类型,它是Monad类型类,它定义了>> = 以及其他一些运算符。按照外行的说法,monad只是定义了>> = 操作的一种类型。



本身>> = 只是一种繁琐的链接函数方式,但由于存在隐藏管道的do-notation,monadic操作会变成是一个非常好的和有用的抽象,在语言中有用的许多地方,并有助于在语言中创建自己的迷你语言。



为什么monad难吗?对于许多Haskell学习者来说,monad是一个障碍,它们像砖墙一样撞击。这并不是monads本身很复杂,而是实现依赖于许多其他高级Haskell功能,如参数化类型,类型类等等。问题是Haskell I / O是基于monad的,I / O可能是你学习一种新语言时首先要理解的东西之一 - 毕竟,创建不产生任何东西的程序并不是很有趣输出。对于这个鸡与鸡蛋问题,我没有立即的解决办法,除非把I / O当作魔术发生在这里,除非你对其他语言部分有足够的经验。对不起。

优秀的monads博客:
http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html


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.

解决方案

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.

You ask for practical examples:

Example 1: List comprehension:

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

This expression returns the doubles of all odd numbers in the range from 1 to 10. Very useful!

It turns out this is really just syntactic sugar for some operations within the List monad. The same list comprehension can be written as:

do
   x <- [1..10]
   if odd x 
       then [x * 2] 
       else []

Or even:

[1..10] >>= (\x -> if odd x then [x*2] else [])

Example 2: Input/Output:

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

Both examples use monads, AKA computation builders. The common theme is that the monad chains operations in some specific, useful way. In the list comprehension, the operations are chained such that if an operation returns a list, then the following operations are performed on every item in the list. The IO monad on the other hand performs the operations sequentially, but passes a "hidden variable" along, which represents "the state of the world", which allows us to write I/O code in a pure functional manner.

It turns out the pattern of chaining operations is quite useful and is used for lots of different things in Haskell.

Another example is exceptions: Using the Error monad, operations are chained such that they are performed sequentially, except if an error is thrown, in which case the rest of the chain is abandoned.

Both the list-comprehension syntax and the do-notation are syntactic sugar for chaining operations using the >>= operator. A monad is basically just a type that supports the >>= operator.

Example 3: A parser

This is a very simple parser which parses either a quoted string or a number:

parseExpr = parseString <|> parseNumber

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

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

The operations char, digit, etc. are pretty simple. They either match or don't match. The magic is the monad which manages the control flow: The operations are performed sequentially until a match fails, in which case the monad backtracks to the latest <|> and tries the next option. Again, a way of chaining operations with some additional, useful semantics.

Example 4: Asynchronous programming

The above examples are in Haskell, but it turns out F# also supports monads. This example is stolen from Don Syme:

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() }

This method fetches a web page. The punch line is the use of GetResponseAsync - it actually waits for the response on a separate thread, while the main thread returns from the function. The last three lines are executed on the spawned thread when the response have been received.

In most other languages you would have to explicitly create a separate function for the lines that handle the response. The async monad is able to "split" the block on its own and postpone the execution of the latter half. (The async {} syntax indicates that the control flow in the block is defined by the async monad.)

How they work

So how can a monad do all these fancy control-flow thing? What actually happens in a do-block (or a computation expression as they are called in F#), is that every operation (basically every line) is wrapped in a separate anonymous function. These functions are then combined using the bind operator (spelled >>= in Haskell). Since the bind operation combines functions, it can execute them as it sees fit: sequentially, multiple times, in reverse, discard some, execute some on a separate thread when it feels like it and so on.

As an example, this is the expanded version of the IO-code from example 2:

putStrLn "What is your name?"
>>= (\_ -> getLine)
>>= (\name -> putStrLn ("Welcome, " ++ name ++ "!"))

This is uglier, but it's also more obvious what is actually going on. The >>= operator is the magic ingredient: It takes a value (on the left side) and combines it with a function (on the right side), to produce a new value. This new value is then taken by the next >>= operator and again combined with a function to produce a new value. >>= can be viewed as a mini-evaluator.

Note that >>= is overloaded for different types, so every monad has its own implementation of >>=. (All the operations in the chain have to be of the type of the same monad though, otherwise the >>= operator won't work.)

The simplest possible implementation of >>= just takes the value on the left and applies it to the function on the right and returns the result, but as said before, what makes the whole pattern useful is when there is something extra going on in the monad's implementation of >>=.

There is some additional cleverness in how the values are passed from one operation to the next, but this requires a deeper explanation of the Haskell type system.

Summing up

In Haskell-terms a monad is a parameterized type which is an instance of the Monad type class, which defines >>= along with a few other operators. In layman's terms, a monad is just a type for which the >>= operation is defined.

In itself >>= is just a cumbersome way of chaining functions, but with the presence of the do-notation which hides the "plumbing", the monadic operations turns out to be a very nice and useful abstraction, useful many places in the language, and useful for creating your own mini-languages in the language.

Why are monads hard?

For many Haskell-learners, monads are an obstacle they hit like a brick wall. It's not that monads themselves are complex, but that the implementation relies on many other advanced Haskell features like parameterized types, type classes, and so on. The problem is that Haskell I/O is based on monads, and I/O is probably one of the first things you want to understand when learning a new language - after all, it's not much fun to create programs which don't produce any output. I have no immediate solution for this chicken-and-egg problem, except treating I/O like "magic happens here" until you have enough experience with other parts of language. Sorry.

Excellent blog on monads: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html

这篇关于什么是monad?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆