就OOP程序员所能理解的(没有任何功能编程背景)而言,什么是monad?

它解决了什么问题,最常用的地方是什么?

使现代化

为了澄清我所寻求的理解,假设您正在将一个具有monad的FP应用程序转换为OOP应用程序。如何将monad的职责移植到OOP应用程序?


当前回答

来自维基百科:

在函数式编程中,monad是一种抽象数据类型,用于表示计算(而不是域模型中的数据)。Monads公司允许程序员链接动作一起构建管道,其中每个动作都用提供了其他处理规则莫纳德。编写的程序功能性风格可以利用monads来构造程序包括顺序操作,1[2]或定义任意控制流(如处理并发,延续或例外)。形式上,monad由定义两个操作(bind和return)和类型构造函数M必须满足几个财产才能允许正确组成一元函数(即使用monad中的值作为参数)。返回操作需要一个普通类型的值,并将其放入装入M型一元容器中。绑定操作执行反向处理,提取集装箱的原始价值,以及将其传递给关联的下一个函数。程序员将编写monadic定义数据处理的函数管道monad充当框架,因为它是一种可重用的行为这决定了调用管道,并管理所有需要的卧底工作计算[3] 绑定和返回管道中交错的运算符将在每个monadic之后执行函数返回控制,并将注意特定方面由monad处理。

我相信这很好地解释了这一点。

其他回答

典型用法中的Monad在功能上等同于过程编程的异常处理机制。

在现代过程语言中,在一系列语句周围放置一个异常处理程序,其中任何语句都可能引发异常。如果任何语句引发异常,则语句序列的正常执行将停止并转移到异常处理程序。

然而,函数式编程语言在哲学上避免了异常处理特性,因为它们的“goto”性质类似。函数编程的观点是,函数不应该有“副作用”,比如中断程序流的异常。

实际上,由于I/O的原因,在现实世界中不能排除副作用。函数式编程中的monad用于处理这一问题,方法是获取一组链式函数调用(其中任何一个都可能产生意外结果),并将任何意外结果转换为封装数据,这些数据仍然可以安全地通过剩余的函数调用。

控制流被保留,但意外事件被安全地封装和处理。

你最近有一篇演讲《Monadologie——关于类型焦虑的专业帮助》(Christopher League,2010年7月12日),这篇演讲对延续和monad的话题非常有趣。这个(幻灯片)演示的视频实际上可以在vimeo上获得。Monad部分开始于37分钟左右,在这段一小时的视频中,从58张幻灯片中的第42张幻灯片开始。

它被称为“函数式编程的主要设计模式”,但示例中使用的语言是Scala,它既是面向对象的又是函数式的。您可以在Debasish Ghosh(2008年3月27日)的博客文章“Monads-在Scala中抽象计算的另一种方法”中阅读更多关于Monad的内容。

如果类型构造函数M支持以下操作,那么它就是monad:

# the return function
def unit[A] (x: A): M[A]

# called "bind" in Haskell 
def flatMap[A,B] (m: M[A]) (f: A => M[B]): M[B]

# Other two can be written in term of the first two:

def map[A,B] (m: M[A]) (f: A => B): M[B] =
  flatMap(m){ x => unit(f(x)) }

def andThen[A,B] (ma: M[A]) (mb: M[B]): M[B] =
  flatMap(ma){ x => mb }

例如(在Scala中):

选项是monad

    def unit[A] (x: A): Option[A] = Some(x)

    def flatMap[A,B](m:Option[A])(f:A =>Option[B]): Option[B] =
      m match {
       case None => None
       case Some(x) => f(x)
      }

列表为Monad

    def unit[A] (x: A): List[A] = List(x)

    def flatMap[A,B](m:List[A])(f:A =>List[B]): List[B] =
      m match {
        case Nil => Nil
        case x::xs => f(x) ::: flatMap(xs)(f)
      }

Monad在Scala中非常重要,因为它是为了利用Monad结构而构建的方便语法:

对于Scala的理解:

for {
  i <- 1 to 4
  j <- 1 to i
  k <- 1 to j
} yield i*j*k

由编译器翻译为:

(1 to 4).flatMap { i =>
  (1 to i).flatMap { j =>
    (1 to j).map { k =>
      i*j*k }}}

关键抽象是flatMap,它通过链接绑定计算。flatMap的每次调用都返回相同的数据结构类型(但值不同),作为链中下一个命令的输入。

在上面的代码段中,flatMap将闭包(SomeType)=>List[AanotherType]作为输入,并返回List[Aanother Type]。需要注意的一点是,所有flatMap都采用相同的闭包类型作为输入,并返回与输出相同的类型。

这就是“绑定”计算线程的原因——为了理解,序列中的每一项都必须遵守相同的类型约束。


如果您执行两个操作(可能失败)并将结果传递给第三个,例如:

lookupVenue: String => Option[Venue]
getLoggedInUser: SessionID => Option[User]
reserveTable: (Venue, User) => Option[ConfNo]

但如果不利用Monad,你会得到复杂的OOP代码,比如:

val user = getLoggedInUser(session)
val confirm =
  if(!user.isDefined) None
  else lookupVenue(name) match {
    case None => None
    case Some(venue) =>
      val confno = reserveTable(venue, user.get)
      if(confno.isDefined)
        mailTo(confno.get, user.get)
      confno
  }

而使用Monad,您可以像所有操作一样使用实际类型(地点、用户),并隐藏选项验证内容,这都是因为for语法的平面图:

val confirm = for {
  venue <- lookupVenue(name)
  user <- getLoggedInUser(session)
  confno <- reserveTable(venue, user)
} yield {
  mailTo(confno, user)
  confno
}

只有当所有三个函数都具有Some[X]时,才会执行屈服部分;任何“无”将直接返回以确认。


So:

Monad允许在函数编程中进行有序计算,这允许我们以一种很好的结构化形式(有点像DSL)对动作序列进行建模。最大的能力来自于将服务于不同目的的monad组合成应用程序中的可扩展抽象的能力。monad对动作的排序和线程化由语言编译器完成,该语言编译器通过闭包的魔力进行转换。


顺便说一句,Monad不是FP中使用的唯一计算模型:

范畴理论提出了许多计算模型。其中计算的Arrow模型莫纳德计算模型计算的应用模型

来自维基百科:

在函数式编程中,monad是一种抽象数据类型,用于表示计算(而不是域模型中的数据)。Monads公司允许程序员链接动作一起构建管道,其中每个动作都用提供了其他处理规则莫纳德。编写的程序功能性风格可以利用monads来构造程序包括顺序操作,1[2]或定义任意控制流(如处理并发,延续或例外)。形式上,monad由定义两个操作(bind和return)和类型构造函数M必须满足几个财产才能允许正确组成一元函数(即使用monad中的值作为参数)。返回操作需要一个普通类型的值,并将其放入装入M型一元容器中。绑定操作执行反向处理,提取集装箱的原始价值,以及将其传递给关联的下一个函数。程序员将编写monadic定义数据处理的函数管道monad充当框架,因为它是一种可重用的行为这决定了调用管道,并管理所有需要的卧底工作计算[3] 绑定和返回管道中交错的运算符将在每个monadic之后执行函数返回控制,并将注意特定方面由monad处理。

我相信这很好地解释了这一点。

如果您曾经使用过Powershell,Eric描述的模式听起来应该很熟悉。Powershell cmdlet是monad;功能组成由管道表示。

杰弗里·斯诺弗对埃里克·梅杰的采访更为详细。

看到我对“什么是monad?”的回答了吗

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

它假设不了解函数式编程,并且使用带有函数(参数):=表达式语法的伪代码和最简单的表达式。

这个C++程序是伪代码monad的一个实现。(仅供参考:M是类型构造函数,feed是“绑定”操作,wrap是“返回”操作。)

#include <iostream>
#include <string>

template <class A> class M
{
public:
    A val;
    std::string messages;
};

template <class A, class B>
M<B> feed(M<B> (*f)(A), M<A> x)
{
    M<B> m = f(x.val);
    m.messages = x.messages + m.messages;
    return m;
}

template <class A>
M<A> wrap(A x)
{
    M<A> m;
    m.val = x;
    m.messages = "";
    return m;
}

class T {};
class U {};
class V {};

M<U> g(V x)
{
    M<U> m;
    m.messages = "called g.\n";
    return m;
}

M<T> f(U x)
{
    M<T> m;
    m.messages = "called f.\n";
    return m;
}

int main()
{
    V x;
    M<T> m = feed(f, feed(g, wrap(x)));
    std::cout << m.messages;
}