我正在尝试在C ++中实现Ana
,Cata
和Bind
(来自this video)。我已经管理了Ana
和Cata
,但是Bind
让我不知所措。
以下是Ana
和Cata
的内容:
#define FEnumerable function<function<Option<T>(void)>(void)>
#define TResult function<function<Option<R>(void)>(void)>
template<typename T> class Option {
public:
bool HasValue;
T value;
Option(T value) : HasValue(true), value(value) { }
Option() : HasValue(false) { }
};
template<typename T> FEnumerable Ana(T seed, function<bool(T)> condition, function<T(T)> next) {
auto result = [condition, next, seed]() -> function<Option<T>()> {
return [condition, next, seed]() -> Option<T> {
static Option<T> value;
value = Option<T>(value.HasValue ? next(value.value) : seed);
if (condition(value.value))
return Option<T>(value.value);
return Option<T>();
};
};
return result;
};
template<typename T, typename R> TResult Ana(T seed, function<bool(T)> condition, function<T(T)> next, function<R(T)> translation) {
auto result = [condition, next, seed, translation]() -> function<Option<R>()> {
return [condition, next, seed, translation]() -> Option<R> {
static Option<T> value;
value = Option<T>(value.HasValue ? next(value.value) : seed);
if (condition(value.value))
return Option<R>(translation(value.value));
return Option<R>();
};
};
return result;
};
template<typename T, typename A> A Cata(FEnumerable source, A seed, function<A(A, T)> fn) {
auto e = source();
A result = seed;
Option<T> value;
while ((value = e()).HasValue)
result = fn(result, value.value);
return result;
};
template<typename T, typename A, typename R> R Cata(FEnumerable source, A seed, function<A(A, T)> fn, function<R(A)> translation) {
auto e = source();
R result = seed;
Option<T> value;
while ((value = e()).HasValue)
result = fn(result, value.value);
return translation(result);
};
到目前为止,我估计我做得很好。这些序列被懒惰地评估(这些日子里使用Yield关键字在.NET中是微不足道的)并且可能是轻量级的。但是,我无法绕过Bind
所需的lambdas:
template<typename T> TResult Bind(FEnumerable source, function<TResult(T)> selector) {
...
}
换句话说,Bind()
需要接受FEnumerable<T>
(延迟序列)和接受单个值并返回值序列的选择器函数;然后Bind必须为每个输入值调用一次选择器函数,然后在一个大的序列中返回选择器返回的所有值。 但是懒洋洋地。作为FEnumerable<R>
。
作为参考,这里是它在C#中的外观:yield:
foreach (var value in source)
foreach (var result in selector(value))
yield return result;
是的,如果没有收益,那就更难了。以下是在没有延迟评估的情况下C ++的外观:
list<R> results;
while ((auto value = source()).HasValue)
while ((auto result = selector(value)).HasValue)
results.push_back(result);
return results;
但是我需要懒惰的评估,这意味着嵌套的lambdas。如果有人在没有头部爆炸的情况下走得这么远,请帮助我。
答案 0 :(得分:1)
我们可以尝试创建一个子源,即迭代它时source
的连续结果,这是闭包状态的一部分:
template<typename T>
TResult Bind(FEnumerable source, function<TResult(T)> selector)
{
return [source, selector]() -> function<Option<R>()>
{
auto e = source();
// Note that std::function is nullable and I am using
// this possible state!
// If this isn't std::function, making subsource an
// Option<function<Option<R>()> is always a possibility
function<Option<R>()> subsource;
return [e, subsource, selector]() -> Option<R>
{
while(!subsource) {
// This means we need to fetch a new subsource
auto candidate = e();
if(!candidate.HasValue) {
// Iteration ends here once `e` has run out
return Option<R>();
} else {
subsource = selector(candidate.value)();
}
auto result = subsource();
if(!result.HasValue) {
// We selected over an empty subsource, so let's
// try again and maybe pick a new fresh one
subsource = nullptr;
continue;
}
return result;
}
};
};
}
请注意,这依赖于e()
重复调用,假设每次迭代过期时返回空Option<T>
。否则,你可以明确地编码'我们已经用完子资源'状态,例如还有一个闭包变量。
也许客户端代码有责任在迭代结束后停止迭代Bind
的结果,在这种情况下你也可以去source
结束/ e
仍然只会到达一次。
答案 1 :(得分:1)
好的,我从Luc Danton那里获取了代码,解决了一些问题并使其正常运行。以下是寻找解决方案的其他人的代码:
template<typename T, typename R> TResult Bind(FEnumerable source, function<TResult(T)> selector) {
return [source, selector]() -> function<Option<R>()> {
auto e = source();
// Note that std::function is nullable and I am using this possible state!
// If this isn't std::function, making subsource an Option<function<Option<R>()> is always a possibility
function<Option<R>()> subsource;
return [e, subsource, selector]() mutable -> Option<R> {
while (true) {
while(!subsource) { // This means we need to fetch a new subsource
auto candidate = e();
if (!candidate.HasValue)
return Option<R>(); // Iteration ends here once `source` has run out
subsource = selector(candidate.value)();
}
auto result = subsource();
if (result.HasValue)
return result;
subsource = nullptr; // We selected over an empty subsource, so let's try again and maybe pick a new fresh one
}
return Option<R>();
};
};
}