首页 文章

如何在一个Linq表达式中混合monadic结构?

提问于
浏览
2

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

Maybe monad看起来像

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 monad . 准确的措辞:

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

我该如何解决这个问题?

3 回答

  • 0

    在我看来,问题在于 ParseEntries 的签名 .

    目前是:

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

    也许它应该是?

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

    因此,不是可能的数组,它应该是一个可能的数组 .

  • 1

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

    from entry       in entries.Value
    

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

  • 0

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

    首先,我需要对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 .

相关问题