Monad用简单的英语? (对于没有FP背景的OOP程序员) [英] Monad in plain English? (For the OOP programmer with no FP background)

查看:121
本文介绍了Monad用简单的英语? (对于没有FP背景的OOP程序员)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

OOP程序员会理解(没有任何函数式编程背景),什么是monad?

它有什么问题解决和它使用最常见的地方是什么?



编辑

为了澄清我正在寻找的那种理解,让我们假设你将具有monad的FP应用程序转换为OOP应用程序。你会做什么来将Monad的责任移交给OOP应用程序?

解决方案

更新:这个问题是一个非常长的博客系列的主题,你可以阅读

  • 绑定操作 - 二进制绑定操作以一个单值和一个函数为参数转换该值,并返回一个新的monadic值。





    • 绑定运算符展开嵌入的plain值它的输入monadic值,并将其提供给函数。
    • 然后函数创建一个新的monadic值,该值可以输入到管道中组成的下一个绑定操作符。



    • 这样可以将非放大类型的操作转换为扩展类型,它服从前面提到的功能组合的规则。

  • 一种将非放大类型从放大类型中取出的方法。 (最后一点对monad来说不是必须的,但通常情况下,这种操作是存在的。)

    同样,以 Nullable< T> 为例。你可以用构造函数把 int 变成 Nullable< int> 。 C#编译器负责为您提供大多数可空的提升,但如果不提升,则提升转换非常简单:一个操作,例如,

      int M(int x){whatever} 

    转换为

     可为空< int> M(Nullable< int> x)
    {
    if(x == null)
    return null;
    else
    return new Nullable< int>(whatever);
    }

    然后转换可为空< int> 回到 int vs.110).aspxrel =noreferrer> Value 属性。



    功能转换是关键。注意可空操作的实际语义 - 对 null 的操作传播 null - 在转型。我们可以概括这一点。假设你有一个从 int int 的函数,就像我们原来的 M 。你可以很容易地将它变成一个函数,它接受一个 int 并返回一个 Nullable< int> ,因为你可以运行结果通过可为空的构造函数。现在假设你有这个高阶方法:
    $ b

     可为空的< T> Bind T(Nullable T扩增,Func T,Nullable T func)
    {
    if(amplified == null)
    return null;
    else
    return func(amplified.Value);
    }

    看看你能用它做什么? 任何需要 int 并返回 int 的方法,或者接受 int 并返回一个 Nullable< int> 现在可以应用可空的语义

    此外:假设你有两个方法

     可为空< int> X(int q){...} 
    可空< int> Y(int r){...}

    并且您想编写它们:

    可为空< int> Z(int s){return X(Y(s)); }

    也就是说, Z X Y 。但是你不能那样做,因为 X 需要一个 int ,并且 Y 返回一个可为空的< int> 。但是,由于您有绑定操作,因此您可以完成这项工作:
    $ b

    可为空< int> ; Z(int s){return Bind(Y(s),X); }

    monad上的绑定操作是使放大类型的函数组合起作用的原因。 我上面提到的规则是monad保留了正常函数组合的规则;与身份函数组成的结果是原始函数,该组合是关联的,等等。

    在C#中,绑定被称为SelectMany。看看它如何在序列monad上工作。我们需要做三件事:将一个值转化为一个序列,将一个序列转化为一个值,并将序列上的操作绑定在一起。这些操作是:
    $ b

      IEnumerable< T> MakeSequence< T>(T item)
    {
    yield return item;
    }
    T单个< T>(IEnumerable< T>序列)
    {
    //让我们取第一个
    foreach项目;
    }
    IEnumerable< T> SelectMany T(IEnumerable seq,Func func)
    {
    foreach(T seq)
    foreach(T result in func item))
    收益率返回结果;

    $ / code>

    可空的monad规则是将两个产生空值的函数组合在一起,检查查看内层结果是否为null;如果是,则产生null,如果不是,则调用外层结果为。这是可空的所需语义。序列monad规则是将两个产生序列的函数结合在一起,将外部函数应用于由内部函数产生的每个元素,然后将所有结果序列连接在一起。 monads的基本语义被捕获在 Bind / SelectMany 方法中;这是告诉你monad真正意味着什么的方法。



    我们可以做得更好。假设你有一系列的整数,并且有一个方法可以获取整数并得到字符串序列。我们可以概括一下绑定操作,以允许构造可以获得并返回不同放大类型的函数,只要其中一个的输入与另一个的输出相匹配即可:

      IEnumerable< U> SelectMany (IEnumerable  seq,Func  func)
    {
    foreach(T seq)
    foreach(U result func(item))
    产量返回结果;
    }

    现在我们可以说将这些个体整体放大成一系列整数,将这个特定的整数转换成一串字符串,放大到一串字符串,现在把这两个操作放在一起:将这一串整数放到所有字符串序列的连接中。 Monads允许你撰写放大。


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


    这就像问单身模式解决了什么问题?,但我会给它一个镜头。

    Monads通常用于解决以下问题:


    • 我需要制作这种类型的新功能,并仍然结合这种类型的旧功能,以使用新功能。
    • 我需要捕获一些类型的操作,并将这些操作表示为可组合对象,建立直到我只有正确的一系列操作代表,然后我需要开始从事物中获得结果

    • 我需要用语言干净地表示副作用操作(注意,这些基本上有三种说法相同的方法)。



    • $ b


      C#在其设计中使用monads。如前所述,可空模式非常类似于可能的单子。 LINQ完全由单子构建; SelectMany 方法是什么操作组成的语义工作。 (Erik Meijer非常喜欢指出每个LINQ函数实际上可以通过 SelectMany ;其他一切只是一种方便。)


      为了澄清我所寻找的理解,让我们假设你将具有monad的FP应用程序转换为OOP应用程序。你会做什么来将monad的职责移植到OOP应用程序中?

      大多数OOP语言没有足够丰富的类型系统直接表示单子模式本身;你需要一个支持类型比泛型更高的类型系统。所以我不会这么做。相反,我会实现代表每个monad的通用类型,并实现代表您需要的三种操作的方法:将值转换为放大的值,将放大的值转换为值,并将未放大的值的函数转换为函数放大的值。

      一个好的开始是我们如何在C#中实现LINQ。研究 SelectMany 方法;了解序列monad如何在C#中工作是关键。这是一个非常简单的方法,但功能非常强大!



      为了更深入理论地解释C#中的monads,我强烈建议我的同事Wes Dyer的文章主题。这篇文章向我解释monads,当他们最终为我点击时。



      Monads的奇迹







      插图来自:

      非常棒的描述性JavaScript与单体演示文稿由Michal Nowak



      一个Hyperturtle Monad制作漂亮的图片文章由 Jonathan Vincent Toups


      In terms that an OOP programmer would understand (without any functional programming background), what is a monad?

      What problem does it solve and what are the most common places it's used?

      EDIT:

      To clarify the kind of understanding I was looking for, let's say you were converting an FP application that had monads into an OOP application. What would you do to port the responsibilities of the monads to the OOP app?

      解决方案

      UPDATE: This question was the subject of an immensely long blog series, which you can read at Monads — thanks for the great question!

      In terms that an OOP programmer would understand (without any functional programming background), what is a monad?

      A monad is an "amplifier" of types that obeys certain rules and which has certain operations provided.

      First, what is an "amplifier of types"? By that I mean some system which lets you take a type and turn it into a more special type. For example, in C# consider Nullable<T>. This is an amplifier of types. It lets you take a type, say int, and add a new capability to that type, namely, that now it can be null when it couldn't before.

      As a second example, consider IEnumerable<T>. It is an amplifier of types. It lets you take a type, say, string, and add a new capability to that type, namely, that you can now make a sequence of strings out of any number of single strings.

      What are the "certain rules"? Briefly, that there is a sensible way for functions on the underlying type to work on the amplified type such that they follow the normal rules of functional composition. For example, if you have a function on integers, say

      int M(int x) { return x + N(x * 2); }
      

      then the corresponding function on Nullable<int> can make all the operators and calls in there work together "in the same way" that they did before.

      (That is incredibly vague and imprecise; you asked for an explanation that didn't assume anything about knowledge of functional composition.)

      What are the "operations"?

      1. Return (also unit) operation — the unary operation that takes a value from a plain type and puts it into a container using the constructor — creating a monadic value. This, in essence, provides a way to take a value of an unamplified type and turn it into a value of the amplified type.
      2. Bind operation — binary bind operation takes as its arguments a monadic value and a function that can transform the value, and returns a new monadic value.
        • The bind operator unwraps the plain value, embedded in its input monadic value, and feeds it to the function.
        • The function then creates a new monadic value, that can be fed to the next bind operators composed in the pipeline.
        • This grants a way to transform operations on the unamplified type into operations on the amplified type, that obeys the rules of functional composition mentioned before.
      3. A way to get the unamplified type back out of the amplified type. (This last point isn't strictly necessary for a monad but it is frequently the case that such an operation exists.)

      Again, take Nullable<T> as an example. You can turn an int into a Nullable<int> with the constructor. The C# compiler takes care of most nullable "lifting" for you, but if it didn't, the lifting transformation is straightforward: an operation, say,

      int M(int x) { whatever }
      

      is transformed into

      Nullable<int> M(Nullable<int> x) 
      { 
          if (x == null) 
              return null; 
          else 
              return new Nullable<int>(whatever);
      }
      

      And turning a Nullable<int> back into an int is done with the Value property.

      It's the function transformation that is the key bit. Notice how the actual semantics of the nullable operation — that an operation on a null propagates the null — is captured in the transformation. We can generalize this. Suppose you have a function from int to int, like our original M. You can easily make that into a function that takes an int and returns a Nullable<int> because you can just run the result through the nullable constructor. Now suppose you have this higher-order method:

      Nullable<T> Bind<T>(Nullable<T> amplified, Func<T, Nullable<T>> func)
      {
          if (amplified == null) 
              return null;
          else
              return func(amplified.Value);
      }
      

      See what you can do with that? Any method that takes an int and returns an int, or takes an int and returns a Nullable<int> can now have the nullable semantics applied to it.

      Furthermore: suppose you have two methods

      Nullable<int> X(int q) { ... }
      Nullable<int> Y(int r) { ... }
      

      and you want to compose them:

      Nullable<int> Z(int s) { return X(Y(s)); }
      

      That is, Z is the composition of X and Y. But you cannot do that because X takes an int, and Y returns a Nullable<int>. But since you have the "bind" operation, you can make this work:

      Nullable<int> Z(int s) { return Bind(Y(s), X); }
      

      The bind operation on a monad is what makes composition of functions on amplified types work. The "rules" I handwaved about above are that the monad preserves the rules of normal function composition; that composing with identity functions results in the original function, that composition is associative, and so on.

      In C#, "Bind" is called "SelectMany". Take a look at how it works on the sequence monad. We need to have three things: turn a value into a sequence, turn a sequence into a value, and bind operations on sequences. Those operations are:

      IEnumerable<T> MakeSequence<T>(T item)
      {
          yield return item;
      }
      T Single<T>(IEnumerable<T> sequence)
      {
          // let's just take the first one
          foreach(T item in sequence) return item; 
      }
      IEnumerable<T> SelectMany<T>(IEnumerable<T> seq, Func<T, IEnumerable<T>> func)
      {
          foreach(T item in seq)
              foreach(T result in func(item))
                  yield return result;            
      }
      

      The nullable monad rule was "to combine two functions that produce nullables together, check to see if the inner one results in null; if it does, produce null, if it does not, then call the outer one with the result". That's the desired semantics of nullable. The sequence monad rule is "to combine two functions that produce sequences together, apply the outer function to every element produced by the inner function, and then concatenate all the resulting sequences together". The fundamental semantics of the monads are captured in the Bind/SelectMany methods; this is the method that tells you what the monad really means.

      We can do even better. Suppose you have a sequences of ints, and a method that takes ints and results in sequences of strings. We could generalize the binding operation to allow composition of functions that take and return different amplified types, so long as the inputs of one match the outputs of the other:

      IEnumerable<U> SelectMany<T,U>(IEnumerable<T> seq, Func<T, IEnumerable<U>> func)
      {
          foreach(T item in seq)
              foreach(U result in func(item))
                  yield return result;            
      }
      

      So now we can say "amplify this bunch of individual integers into a sequence of integers. Transform this particular integer into a bunch of strings, amplified to a sequence of strings. Now put both operations together: amplify this bunch of integers into the concatenation of all the sequences of strings." Monads allow you to compose your amplifications.

      What problem does it solve and what are the most common places it's used?

      That's rather like asking "what problems does the singleton pattern solve?", but I'll give it a shot.

      Monads are typically used to solve problems like:

      • I need to make new capabilities for this type and still combine old functions on this type to use the new capabilities.
      • I need to capture a bunch of operations on types and represent those operations as composable objects, building up larger and larger compositions until I have just the right series of operations represented, and then I need to start getting results out of the thing
      • I need to represent side-effecting operations cleanly in a language that hates side effects

      (Note that these are basically three ways of saying the same thing.)

      C# uses monads in its design. As already mentioned, the nullable pattern is highly akin to the "maybe monad". LINQ is entirely built out of monads; the SelectMany method is what does the semantic work of composition of operations. (Erik Meijer is fond of pointing out that every LINQ function could actually be implemented by SelectMany; everything else is just a convenience.)

      To clarify the kind of understanding I was looking for, let's say you were converting an FP application that had monads into an OOP application. What would you do to port the responsibilities of the monads into the OOP app?

      Most OOP languages do not have a rich enough type system to represent the monad pattern itself directly; you need a type system that supports types that are higher types than generic types. So I wouldn't try to do that. Rather, I would implement generic types that represent each monad, and implement methods that represent the three operations you need: turning a value into an amplified value, turning an amplified value into a value, and transforming a function on unamplified values into a function on amplified values.

      A good place to start is how we implemented LINQ in C#. Study the SelectMany method; it is the key to understanding how the sequence monad works in C#. It is a very simple method, but very powerful!

      For a more in-depth and theoretically sound explanation of monads in C#, I highly recommend my colleague Wes Dyer's article on the subject. This article is what explained monads to me when they finally "clicked" for me.

      The Marvels of Monads


      Illustrations are borrowed from:
      Awesomely descriptive JavaScript with monads presentation by Michal Nowak
      and
      A Hyperturtle Monad Makes Pretty Pictures article by Jonathan Vincent Toups.

      这篇关于Monad用简单的英语? (对于没有FP背景的OOP程序员)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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