C# 如何在单个Linq表达式中混合一元结构?
我有一个C中Maybe monad的玩具实现,并实现了与Linq相关的许多扩展方法。当我试图在一个Linq语句中混合使用IEnumerable和IMaybe时,我偶然发现了一个问题 也许monad看起来像C# 如何在单个Linq表达式中混合一元结构?,c#,linq,functional-programming,monads,C#,Linq,Functional Programming,Monads,我有一个C中Maybe monad的玩具实现,并实现了与Linq相关的许多扩展方法。当我试图在一个Linq语句中混合使用IEnumerable和IMaybe时,我偶然发现了一个问题 也许monad看起来像 public interface IMaybe<T> { bool HasValue { get; } T Value { get; } } public static class Maybe { class SomeImpl<T>: IMay
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单子。确切地说:
错误1源类型为“MonadicSharp.IMaybe”的查询表达式的后续from子句中不允许使用类型为“System.Collections.Generic.KeyValuePair[]”的表达式。调用“SelectMany”时类型推断失败。C:\Dev\Local\MonadicSharp\MonadicSharp\Program.cs 142 31 MonadicSharp
我该如何解决这个问题?我认为问题在于条目的类型是IMaybe,而不是IEnumerable。 你有没有试过这样的方法:
from entry in entries.Value
当然,这不是Monad的目的,但这应该是第一步。在我看来,问题在于ParseEntries的签名 目前是:
static IMaybe<KeyValuePair<string, Uri>[]> ParseEntries(string input);
也许应该是这样
static IMaybe<KeyValuePair<string, Uri>>[] ParseEntries(string input);
因此,与其说是maybe of数组,不如说是maybe数组。经过一些研究,我得出结论,在一个LINQ语句中混合单子是不可能的,所以我决定将它分成两个语句。这就是它的工作原理: 首先,我需要对IMaybe接口声明稍作修改,以使用协方差:
public interface IMaybe<out T>{ ... }
如您所见,第一条LINQ语句适用于IMaybe monad,第二条适用于IEnumerable。好吧,也许我在这里只是达到了知识的极限。我要回去读关于monad transformer和Haskell的书。当我得到一个线索时,我会进一步评论这个问题。只是一个小小的提示-我会通过避免IMaybe来简化,并且只会有一个具体类型的Maybe。很多东西都可以实现IEnumerable,这是有道理的,但我想不出需要多个IMaybe实现。可以吗?条目的类型为KeyValuePair[],因此无法编译。请记住,闭包是由SelectMany扩展方法引入的,并且已经展开。真正的问题是没有通用的方法来混合单子,因为这样做的语义没有很好的定义。这与我的解决方案非常接近。ParseEntries的基本语义是,如果输入符合特定语法,则返回一个条目数组,否则不返回任何内容,因此方法的签名仍然有效。如您所见,我决定引入另一种方法将IMaybe转换为IMaybe[]。这类似于Task.WhenAll的反面,它将Task[]转换为Task。谢谢你的建议。
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));
}
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)
{
//...
}
}