【问题标题】:Combining predicates in a functional way and allowing short-circuiting to work以功能方式组合谓词并允许短路工作
【发布时间】:2021-05-13 12:14:39
【问题描述】:

前言

我问a similar 问题:假设我有一个谓词auto p1 = [](int x){ return x > 2; } 和一个谓词auto p2 = [](int x){ return x < 6; },我如何结合p1p2 得到p1and2 这样p1and2(x) == p1(x) && p2(x)?答案是使用boost::hana::demux(有关详细信息,请参阅链接的问题)。

新问题和问题

然而,有时,只有当另一个谓词评估为给定的真实性值时,才应该对一个谓词进行评估,例如true.

例如,一个谓词可能是

constexpr auto has_value = [](std::optional<int> opt){ return opt.has_value(); };

和其他谓词

constexpr auto has_positive = [](std::optional<int> opt){ return opt.value() == 3; };

以下内容很容易识别

bool b1 = has_value(some_opt_int) && has_positive(some_opt_int); // true or false, but just fine either way
bool b2 = has_positive(some_opt_int) && has_value(some_opt_int); // runtime error if !some_opt_int.has_value()

定义

constexpr auto all = boost::hana::demux([](auto const&... x) { return (x && ...); });

并像这样使用它

std::optional<int> empty{};
contexpr auto has_value_which_is_positive = all(has_value, has_positive);
bool result = has_value_which_is_positive(empty);

会导致运行时失败,因为the it's the function call to the variadic generic lambda wrapped in all which forces the evaluation of its arguments, not the fold expression (x &amp;&amp; ...)

所以我的问题是,我如何结合has_valuehas_positive 得到has_value_which_is_positive?更一般地说,我如何将几个谓词“和”在一起,以便仅在短路机制所需的范围内对它们进行评估?

我的尝试

我认为,为了防止谓词被评估,我可以将它们包装在一些函数对象中,当应用于参数(std::optional)时,返回另一个包装谓词和std::optional 在一起,并且有一个 operator bool 转换函数,只有在计算折叠表达式时才会触发。

这是我的尝试,这是可悲的未定义行为,因为main 中的断言有时会失败,有时不会:

#include <optional>

#include <boost/hana/functional/demux.hpp>
#include <boost/hana/functional/curry.hpp>

template<typename P, typename T>
struct LazilyAppliedPred {
    LazilyAppliedPred(P const& p, T const& t)
        : p(p)
        , t(t)
    {}
    P const& p;
    T const& t;
    operator bool() const {
        return p(t);
    }
};

constexpr auto lazily_applied_pred = [](auto const& p, auto const& t) {
    return LazilyAppliedPred(p,t);
};

auto constexpr lazily_applied_pred_curried = boost::hana::curry<2>(lazily_applied_pred);

constexpr auto all_true = [](auto const&... x) { return (x && ...); };

constexpr auto all = boost::hana::demux(all_true);

constexpr auto has_value = [](std::optional<int> o){
    return o.has_value();
};
constexpr auto has_positive = [](std::optional<int> o){
    assert(o.has_value());
    return o.value() > 0;
};

int main() {
    assert(all(lazily_applied_pred_curried(has_value),
               lazily_applied_pred_curried(has_positive))(std::optional<int>{2}));
}

(Follow up question.)

【问题讨论】:

    标签: c++ functional-programming lazy-evaluation short-circuiting boost-hana


    【解决方案1】:

    我刚刚意识到执行我所描述的操作的临时 lambda 实际上非常简洁:

    #include <assert.h>
    #include <optional>
    
    constexpr auto all = [](auto const& ... predicates){
        return [&predicates...](auto const& x){
            return (predicates(x) && ...);
        };
    };
    
    constexpr auto has_value = [](std::optional<int> o){
        return o.has_value();
    };
    constexpr auto has_positive = [](std::optional<int> o){
        assert(o.has_value());
        return o.value() > 0;
    };
    
    int main() {
        assert(all(has_value, has_positive)(std::optional<int>{2}));
        assert(!all(has_value, has_positive)(std::optional<int>{}));
    }
    

    但是,there's still something about short-circuiting in fold expressions that doesn't convince me...

    【讨论】:

      【解决方案2】:

      也许你的问题比我能理解的更微妙,但这个非常简单的解决方案似乎可以解决问题(据我所知)。

      /**
        g++ -std=c++17 -o prog_cpp prog_cpp.cpp \
            -pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion \
            -g -O0 -UNDEBUG -fsanitize=address,undefined
      **/
      
      #include <iostream>
      #include <optional>
      
      template<typename Predicate1,
               typename Predicate2>
      auto
      lazy_and(Predicate1 p1,
               Predicate2 p2)
      {
        return [&](const auto &arg)
        {
          return p1(arg)&&p2(arg);
        };
      }
      
      int
      main()
      {
        // basic predicates
        const auto has_value=[&](const auto &opt){ return opt.has_value(); };
        const auto is_positive=[&](const auto &opt){ return opt.value()>0; };
        // combination of predicates
        const auto has_value_and_is_positive=lazy_and(has_value, is_positive);
        const auto is_positive_and_has_value=lazy_and(is_positive, has_value);
        // test
        const auto test_predicate=
          [&](const auto &title, const auto &predicate, const auto &arg)
          {
            try
            {
              const auto result=predicate(arg);
              std::cout << title << " --> " << result << '\n';
            }
            catch(const std::exception &e)
            {
              std::cerr << title << " !!! " << e.what() << '\n';
            }
          };
        std::cout << "~~~~ empty ~~~~\n";
        const auto empty=std::optional<int>{};
        test_predicate("has_value",
                       has_value, empty);
        test_predicate("is_positive",
                       is_positive, empty);
        test_predicate("has_value_and_is_positive",
                       has_value_and_is_positive, empty);
        test_predicate("is_positive_and_has_value",
                       is_positive_and_has_value, empty);
        std::cout << "~~~~ positive ~~~~\n";
        const auto positive=std::optional<int>{5};
        test_predicate("has_value",
                       has_value, positive);
        test_predicate("is_positive",
                       is_positive, positive);
        test_predicate("has_value_and_is_positive",
                       has_value_and_is_positive, positive);
        test_predicate("is_positive_and_has_value",
                       is_positive_and_has_value, positive);
        std::cout << "~~~~ negative ~~~~\n";
        const auto negative=std::optional<int>{-3};
        test_predicate("has_value",
                       has_value, negative);
        test_predicate("is_positive",
                       is_positive, negative);
        test_predicate("has_value_and_is_positive",
                       has_value_and_is_positive, negative);
        test_predicate("is_positive_and_has_value",
                       is_positive_and_has_value, negative);
      }
      
      /**
      ~~~~ empty ~~~~
      has_value --> 0
      is_positive !!! bad optional access
      has_value_and_is_positive --> 0
      is_positive_and_has_value !!! bad optional access
      ~~~~ positive ~~~~
      has_value --> 1
      is_positive --> 1
      has_value_and_is_positive --> 1
      is_positive_and_has_value --> 1
      ~~~~ negative ~~~~
      has_value --> 1
      is_positive --> 0
      has_value_and_is_positive --> 0
      is_positive_and_has_value --> 0
      **/
      

      【讨论】:

        猜你喜欢
        • 2021-01-23
        • 2020-10-02
        • 2010-10-07
        • 1970-01-01
        • 1970-01-01
        • 2015-11-19
        • 2019-08-01
        • 2018-11-13
        • 1970-01-01
        相关资源
        最近更新 更多