【发布时间】:2020-12-29 19:15:08
【问题描述】:
我有一个从数据存储中异步查找项目的方法;
class MyThing {}
Task<Try<MyThing>> GetThing(int thingId) {...}
我想从数据存储中查找多个项目,并编写了一个新方法来执行此操作。我还编写了一个辅助方法,它将采用多个 Try<T> 并将它们的结果组合成一个 Try<IEnumerable<T>>。
public static class TryExtensions
{
Try<IEnumerable<T>> Collapse<T>(this IEnumerable<Try<T>> items)
{
var failures = items.Fails().ToArray();
return failures.Any() ?
Try<IEnumerable<T>>(new AggregateException(failures)) :
Try(items.Select(i => i.Succ(a => a).Fail(Enumerable.Empty<T>())));
}
}
async Task<Try<MyThing[]>> GetThings(IEnumerable<string> ids)
{
var results = new List<Try<Things>>();
foreach (var id in ids)
{
var thing = await GetThing(id);
results.Add(thing);
}
return results.Collapse().Map(p => p.ToArray());
}
另一种方法是这样的;
async Task<Try<MyThing[]>> GetThings(IEnumerable<string> ids)
{
var tasks = ids.Select(async id => await GetThing(id)).ToArray();
await Task.WhenAll(tasks);
return tasks.Select(t => t.Result).Collapse().Map(p => p.ToArray());
}
这样做的问题是所有任务都将并行运行,我不想用大量并行请求来冲击我的数据存储。我真正想要的是使用LanguageExt 的一元原则和特性使我的代码功能化。有谁知道如何做到这一点?
更新
感谢@MatthewWatson 的建议,这就是SemaphoreSlim 的样子;
async Task<Try<MyThing[]>> GetThings(IEnumerable<string> ids)
{
var mutex = new SemaphoreSlim(1);
var results = ids.Select(async id =>
{
await mutex.WaitAsync();
try { return await GetThing(id); }
finally { mutex.Release(); }
}).ToArray();
await Task.WhenAll(tasks);
return tasks.Select(t => t.Result).Collapse().Map(Enumerable.ToArray);
return results.Collapse().Map(p => p.ToArray());
}
问题是,这仍然不是很单子/功能性,并且最终的代码行数比带有foreach 块的原始代码多。
【问题讨论】:
-
你可以使用
SemaphoreSlim来限制,例如stackoverflow.com/a/57557324/106159 -
“更少”代码行似乎是一个相当随意的要求。这样做的目的是什么?可以接受多少行代码?
-
对不起@TimRutter。我的要求是使我的代码“功能化”,即使用
Map、Match、Bind等的组合。无论如何,我认为使用 foreach 的版本更“整洁”(无论数量多少行),但这只是我的看法。 -
为什么您认为(根据我的理解;对函数式编程不太熟悉)您的更新中的代码不起作用?功能性意味着它不会改变状态,单子不只是意味着它需要一个参数吗?
-
我以某种方式相信我可以通过执行
return ids.Select(dosomething).Map(dosomethingelse).Bind(athirdthing);来实现上述目标,而无需编写任何循环。社区说函数式编程中不需要循环 - qr.ae/pNC6fQ
标签: c# functional-programming monads language-ext