如何在单个 Linq 表达式中混合一元构造

本文关键字:合一 混合 表达式 单个 Linq | 更新日期: 2023-09-27 18:33:55

我有一个在C#中实现的可能monad,并且已经实现了相关的SelectMany扩展方法来与Linq一起使用。当我尝试在单个 Linq 语句中混合 IEnumerable 和 IMaybe 时,我偶然发现了一个问题。

也许莫纳德看起来像

public interface IMaybe<T>
{
    bool HasValue { get; }
    T Value { get; }
}
public static class Maybe
{
    class SomeImpl<T>: IMaybe<T> // obvious implementation snipped for brevity
    class NoneImpl<T>: IMaybe<T> // obvious implementation snipped for brevity
    // methods to construct the Maybe monad
    public static Wrap<T> Some<T>(T value);
    public static Wrap<T> Some<T>(T? value) where T: struct;
    public static IMaybe<T> None<T>();
    public static IMaybe<B> SelectMany<A, B>(this IMaybe<A> a, Func<A, IMaybe<B>> mapFn)
    {
        if (a.HasValue)
            return mapFn(a.Value);
        else
            return None<B>();
    }
    public static IMaybe<C> SelectMany<A, B, C>(
        this IMaybe<A> a, Func<A, IMaybe<B>> mapFn, Func<A, B, C> selector)
    {
        if (a.HasValue)
        {
            var b = mapFn(a.Value);
            if (b.HasValue)
                return Some(selector(a.Value, b.Value));
            else
                return None<C>();
        }
        else
            return None<C>();
    }
}

我的程序尝试读取文件,将内容解析为多个 URI 条目,然后为每个条目从 URI 下载内容。这些操作的确切实施方式无关紧要。我遇到的麻烦在于将这些操作链接在 Linq 语句中。即

    static IMaybe<string> ReadFile(string path);
    static IMaybe<KeyValuePair<string, Uri>[]> ParseEntries(string input);
    static IMaybe<string> Download(Uri location);
    static void Main(string[] args)
    {
        var result = // IEnumerable<IMaybe<anonymous type of {Key, Content}>>
            from fileContent in ReadFile(args[0])
            from entries     in ParseEntries(fileContent)
            from entry       in entries                   // this line won't compile
            from download    in Download(entry.Value)
            select new { Key = entry.Key, Content = download };
        // rest of program snipped off for brevity
    }

有问题的错误抱怨混合了IMaybe和IEnumerable monads。确切的措辞是:

错误 1 在源类型为"MonadicSharp.IMaybe"的查询表达式的后续 from 子句中不允许使用类型为"System.Collections.Generic.KeyValuePair[]"的表达式。 类型推断在调用"选择多个"时失败。 C:''Dev''Local''MonadicSharp''MonadicSharp''Program.cs 142 31 MonadicSharp

我该如何解决这个问题?

如何在单个 Linq 表达式中混合一元构造

在我看来

,问题出在ParseEntries的签名上。

目前是:

static IMaybe<KeyValuePair<string, Uri>[]> ParseEntries(string input);

也许应该是?

static IMaybe<KeyValuePair<string, Uri>>[] ParseEntries(string input);

因此,它应该是一个 may 数组,而不是数组的 maybe。

我认为问题是因为entries属于IMaybe<T>类型而不是IEnumerable<T>类型。
你有没有尝试过这样的事情:

from entry       in entries.Value

当然,这不是Monad的目的,但这应该是第一步。

经过一些研究,我得出结论,在单个 LINQ 语句中混合 monads 是不可能的,所以我决定将其分成两个语句。这是它的工作原理:

首先,我需要对 IMaybe 接口声明进行轻微更改以使用协方差:

public interface IMaybe<out T>{ ... }

接下来,我需要一些辅助方法来将 IMaybe monad 转换为 IEnumerable monad:

public static IEnumerable<IMaybe<T>> UnfoldAll<T>(
    this IMaybe<IEnumerable<T>> source)
{
    if (source.HasValue)
        return Enumerable.Range(0, 1).Select(i => Maybe.None<T>());
    else
        return source.Value.Select(value => Maybe.Some(value));
}

最后,我将原始 LINQ 语句分解为两个语句(嵌套 LINQ 表达式也可以(

static void Main(string[] args)
{
    var path = args[0];
    var theEntries =
        from fileContent in ReadFile(path)
        from entries in ParseEntries(fileContent)
        select entries;
    var theContents = 
        from entry in theEntries.UnfoldAll() 
        where entry.HasValue 
        select Download(entry.Value.Value);
    foreach (var content in theContents)
    {
        //...
    }
}

如您所见,第一个 LINQ 语句适用于 IMaybe monad,第二个语句适用于 IEnumerable。