在没有yield的情况下实现Bind()(LINQ中的SelectMany)(在C ++中)

时间:2013-05-26 01:16:21

标签: c++ .net bind linq

我正在尝试在C ++中实现AnaCataBind(来自this video)。我已经管理了AnaCata,但是Bind让我不知所措。

以下是AnaCata的内容:

#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。如果有人在没有头部爆炸的情况下走得这么远,请帮助我。

2 个答案:

答案 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>();
        };
    };
}