C# 中的 Monad——为什么 Bind 实现需要传递函数来返回 monad?

发布于 2024-12-11 01:34:39 字数 820 浏览 0 评论 0 原文

我在 C# 中看到的大多数 monad 示例都有点像这样编写:

public static Identity<B> Bind<A, B>(this Identity<A> a, Func<A, Identity<B>> func) {
    return func(a.Value);
}

例如,请参阅 http://mikehadlow.blogspot.com/2011/01/monads-in-c-3-creating-our-first-monad.html

问题是,要求 func 返回 Identity 有何意义?如果我使用以下定义:

public interface IValue<A> {
    public IValue<B> Bind<B>(Func<A, B> func)
}

那么我实际上可以使用相同的 func for Lazy, Task, 也许等实际上并不依赖于实现IValue的实际类型。

我在这里缺少什么重要的东西吗?

Most examples of monads I saw in C# are written somewhat like that:

public static Identity<B> Bind<A, B>(this Identity<A> a, Func<A, Identity<B>> func) {
    return func(a.Value);
}

For example, see http://mikehadlow.blogspot.com/2011/01/monads-in-c-3-creating-our-first-monad.html.

The question is, what is the point of requiring func to return an Identity<B> ? If I use the following definition:

public interface IValue<A> {
    public IValue<B> Bind<B>(Func<A, B> func)
}

then I can actually use same func for for Lazy<T>, Task<T>, Maybe<T> etc without actually depending on actual type implementing IValue.

Is there something important I am missing here?

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

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

发布评论

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

评论(1

碍人泪离人颜 2024-12-18 01:34:39

首先,考虑组合的概念。我们可以轻松地将组合表达为对委托的操作:

public static Func<T, V> Compose<T, U, V>(this Func<U, V> f, Func<T, U> g)
{
    return x => f(g(x));
}

因此,如果我有一个函数 g ,它是 (int x) =>; x.ToString() 和一个函数 f,即 (string s) => s.Length 然后我可以创建一个组合函数 h ,它是 (int x) => x.ToString().Length 通过调用 f.Compose(g) 来实现。

这应该很清楚。

现在假设我有一个从 TMonad 的函数 g 和一个从 UMonadF 的函数 f /代码>。我希望编写一个方法,将这两个返回 monad 的函数组合成一个接受 T 并返回 Monad 的函数。所以我尝试这样写:

public static Func<T, Monad<V>> Compose<T, U, V>(this Func<U, Monad<V>> f, Func<T, Monad<U>> g)
{
    return x => f(g(x));
}

行不通。 g 返回一个 Monad,但 f 接受一个 U。我有一种方法可以将 U “包装”到 Monad 中,但我没有办法“解开”它。

但是,如果我有一个方法

public static Monad<V> Bind<U, V>(this Monad<U> m, Func<U, Monad<V>> k)
{ whatever }

,那么我可以编写一个由两个都返回 monad 的方法组成的方法:

public static Func<T, Monad<V>> Compose<T, U, V>(this Func<U, Monad<V>> f, Func<T, Monad<U>> g)
{
    return x => Bind(g(x), f);
}

这就是 Bind 从 TMonadMonad 获取 func 的原因。 U>——因为事情的重点是能够将函数 g 从 T 获取到 Monad 以及函数 f UMonad 并将它们组合成从 TMonad 的函数 h。

如果您想将函数 g 从 T 转换为 U 并将函数 f 从 U 转换为 Monad > 那么你一开始就不需要绑定。只需正常组合函数即可获得从 TMonad 的方法! Bind的全部目的就是为了解决这个问题;如果您消除了该问题,那么您一开始就不需要 Bind。

更新:

在大多数情况下,我想将函数 g 从 T 组合到 Monad 并将函数 f 从 U 组合到 V


我假设您想将其组合成一个从 TV 的函数。但你不能保证这样的操作被定义了!例如,将“Maybe monad”作为 monad,在 C# 中表示为 T?。假设 g 为 (int x)=>(double?)null 并且函数 f 为 (double y)=>(decimal)y 。您应该如何将 f 和 g 组合成一个接受 int 并返回不可为 null 的 decimal 类型的方法?没有“解包”将可为空的双精度值解包为 f 可以采用的双精度值!

您可以使用 Bind 将 f 和 g 组合成一个接受 int 并返回可为 null 的小数的方法:

public static Func<T, Monad<V>> Compose<T, U, V>(this Func<U, V> f, Func<T, Monad<U>> g)
{
    return x => Bind(g(x), x=>Unit(f(x)));
}

其中 Unit 是接受 V 并返回 MonadMonad 的函数。代码>.

但是,如果 g 返回一个 monad 并且 f 不返回该 monad,那么 f 和 g 根本就没有组合——不能保证有一种方法可以从 monad 的实例返回到“未包装”类型。也许在某些 monad 的情况下总是存在的——比如 Lazy。或者有时可能存在,就像“也许”单子一样。通常有一种方法可以做到这一点,但没有要求您可以这样做。

顺便说一句,请注意我们如何使用“Bind”作为瑞士军刀来制作一种新的构图。绑定后可以进行任何操作!例如,假设我们对序列 monad 进行 Bind 操作,我们在 C# 中的 IEnumerable 类型上将其称为“SelectMany”:

static IEnumerable<V> SelectMany<U, V>(this IEnumerable<U> sequence, Func<U, IEnumerable<V>> f)
{
    foreach(U u in sequence)
        foreach(V v in f(u))
            yield return v;
}

您可能还对序列有一个运算符:

static IEnumerable<A> Where<A>(this IEnumerable<A> sequence, Func<A, bool> predicate)
{
    foreach(A item in sequence)
        if (predicate(item)) 
            yield return item;
}

您真的需要吗将该代码写入 Where 中?不!您可以完全用“Bind/SelectMany”来构建它:

static IEnumerable<A> Where<A>(this IEnumerable<A> sequence, Func<A, bool> predicate)
{
    return sequence.SelectMany((A a)=>predicate(a) ? new A[] { a } : new A[] { } );  
}

高效吗?不,但是没有什么是 Bind/SelectMany 做不到的。如果您确实愿意,您可以只用 SelectMany 构建所有 LINQ 序列运算符。

First off, consider the notion of composition. We can express composition as an operation on delegates easily:

public static Func<T, V> Compose<T, U, V>(this Func<U, V> f, Func<T, U> g)
{
    return x => f(g(x));
}

So if I have a function g which is (int x) => x.ToString() and a function f which is (string s) => s.Length then I can make a composed function h which is (int x) => x.ToString().Length by calling f.Compose(g).

That should be clear.

Now suppose I have a function g from T to Monad<U> and a function f from U to Monad<V>. I wish to write a method that composes these two functions that return monads into a function that takes a T and returns a Monad<V>. So I try to write that:

public static Func<T, Monad<V>> Compose<T, U, V>(this Func<U, Monad<V>> f, Func<T, Monad<U>> g)
{
    return x => f(g(x));
}

Doesn't work. g returns a Monad<U> but f takes a U. I have a way to "wrap" a U into a Monad<U> but I don't have a way to "unwrap" one.

However, if I have a method

public static Monad<V> Bind<U, V>(this Monad<U> m, Func<U, Monad<V>> k)
{ whatever }

then I can write a method that composes two methods that both return monads:

public static Func<T, Monad<V>> Compose<T, U, V>(this Func<U, Monad<V>> f, Func<T, Monad<U>> g)
{
    return x => Bind(g(x), f);
}

That's why Bind takes a func from T to Monad<U> -- because the whole point of the thing is to be able to take a function g from T to Monad<U> and a function f from U to Monad<V> and compose them into a function h from T to Monad<V>.

If you want to take a function g from T to U and a function f from U to Monad<V> then you don't need Bind in the first place. Just compose the functions normally to get a method from T to Monad<V>! The whole purpose of Bind is to solve this problem; if you wave that problem away then you don't need Bind in the first place.

UPDATE:

In most cases I want to compose function g from T to Monad<U> and function f from U to V.

And I presume you then want to compose that into a function from T to V. But you can't guarantee that such an operation is defined! For example, take the "Maybe monad" as the monad, which is expressed in C# as T?. Suppose you have g as (int x)=>(double?)null and you have a function f that is (double y)=>(decimal)y. How are you supposed to compose f and g into a method that takes an int and returns the non-nullable decimal type? There is no "unwrapping" that unwraps the nullable double into a double value that f can take!

You can use Bind to compose f and g into a method that takes an int and returns a nullable decimal:

public static Func<T, Monad<V>> Compose<T, U, V>(this Func<U, V> f, Func<T, Monad<U>> g)
{
    return x => Bind(g(x), x=>Unit(f(x)));
}

where Unit is a function that takes a V and returns a Monad<V>.

But there simply is no composition of f and g if g returns a monad and f doesn't return the monad -- there is no guarantee that there is a way to go back from the instance of the monad to an "unwrapped" type. Maybe in the case of some monads there always is -- like Lazy<T>. Or maybe there sometimes is, like with the "maybe" monad. There often is a way to do it, but there is not a requirement that you can do so.

Incidentally, notice how we just used "Bind" as a Swiss Army Knife to make a new kind of composition. Bind can make any operation! For example, suppose we have the Bind operation on the sequence monad, which we call "SelectMany" on the IEnumerable<T> type in C#:

static IEnumerable<V> SelectMany<U, V>(this IEnumerable<U> sequence, Func<U, IEnumerable<V>> f)
{
    foreach(U u in sequence)
        foreach(V v in f(u))
            yield return v;
}

You might also have an operator on sequences:

static IEnumerable<A> Where<A>(this IEnumerable<A> sequence, Func<A, bool> predicate)
{
    foreach(A item in sequence)
        if (predicate(item)) 
            yield return item;
}

Do you really need to write that code inside Where? No! You can instead build it entirely out of "Bind/SelectMany":

static IEnumerable<A> Where<A>(this IEnumerable<A> sequence, Func<A, bool> predicate)
{
    return sequence.SelectMany((A a)=>predicate(a) ? new A[] { a } : new A[] { } );  
}

Efficient? No. But there is nothing that Bind/SelectMany cannot do. If you really wanted to you could build all of the LINQ sequence operators out of nothing but SelectMany.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文