我在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类型为&#39; System.Collections.Generic.KeyValuePair []&#39;的表达式在源类型为&#39; MonadicSharp.IMaybe&#39;的查询表达式中的后续from子句中不允许使用。在对SelectMany&#39;的调用中,类型推断失败。 C:\ Dev \ Local \ MonadicSharp \ MonadicSharp \ Program.cs 142 31 MonadicSharp
我该如何解决这个问题?
答案 0 :(得分:1)
在我看来,问题在于ParseEntries
的签名。
目前是:
static IMaybe<KeyValuePair<string, Uri>[]> ParseEntries(string input);
也许它应该是?
static IMaybe<KeyValuePair<string, Uri>>[] ParseEntries(string input);
因此,不应该是数组,它应该是一个可能的数组。
答案 1 :(得分:0)
我认为问题是因为entries
属于IMaybe<T>
类型,而不属于IEnumerable<T>
类型。
你尝试过这样的事情:
from entry in entries.Value
当然,这不是Monad的目的,但这应该是第一步。
答案 2 :(得分: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。