Haskell风格“可能”类型& *在C ++ 11中链接*

时间:2011-10-07 17:40:07

标签: c++ c++11

在我的工作项目中,我反复发现自己需要Haskell样式Maybe(特别是可能链接)。例如。来自客户的提款请求,我们获得客户ID ...在缓存中查找客户...如果找到客户...查找她的储蓄账户...如果有账户...退出...在任何时候在这个链中,如果存在查找失败,则不执行任何操作并返回失败。

我的链条很大...有时长达6 ...所以这是我在C ++ 0x中Haskell.Data.Maybe的滑动...(注意......如果我停止,这应该在C ++中工作使用可变参数模板)。我已经开始为自由函数链接一个参数或成员函数没有参数,我对接口感到满意。但是,对于采用多个参数的函数...我必须编写一个lambda函数来模拟部分应用程序。有没有办法避免它?请参阅main()的最后一行。即使它是未注释的,它也不会编译,而是用于const / non-const混合。但问题仍然存在。

对于大量的代码感到抱歉...我希望这不会让那些可能对此感兴趣的人感到沮丧......

#include <iostream>
#include <map>
#include <deque>
#include <algorithm>
#include <type_traits>

typedef long long int int64;

namespace monad { namespace maybe {

  struct Nothing {};

  template < typename T >
  struct Maybe {
    template < typename U, typename Enable = void >
    struct ValueType {
      typedef U * const type;
    };

    template < typename U >
    struct ValueType < U, typename std::enable_if < std::is_reference < U >::value >::type > {
      typedef typename std::remove_reference < T >::type * const type;
    };

    typedef typename ValueType < T >::type value_type;

    value_type m_v;

    Maybe(Nothing const &) : m_v(0) {}

    struct Just {
      value_type m_v;
      Just() = delete;
      explicit Just(T &v) : m_v(&v) {
      }
    };

    Maybe(Just const &just) : m_v(just.m_v) {
    }
  };

  Nothing nothing() {
    return Nothing();
  }

  template < typename T >
  Maybe < T > just(T &v) {
    return typename Maybe < T >::Just(v);
  }

  template < typename T >
  Maybe < T const > just(T const &v) {
    return typename Maybe < T const >::Just(v);
  }

  template < typename T, typename R, typename A >
  Maybe < R > operator | (Maybe < T > const &t, R (*f)(A const &)) {
    if (t.m_v)
      return just < R >(f(*t.m_v));
    else
      return nothing();
  }

  template < typename T, typename R, typename A >
  Maybe < R > operator | (Maybe < T > const &t, Maybe < R > (*f)(A const &)) {
    if (t.m_v)
      return f(*t.m_v);
    else
      return nothing();
  }

  template < typename T, typename R, typename A >
  Maybe < R > operator | (Maybe < T > const &t, R (*f)(A &)) {
    if (t.m_v)
      return just < R >(f(*t.m_v));
    else
      return nothing();
  }

  template < typename T, typename R, typename A >
  Maybe < R > operator | (Maybe < T > const &t, Maybe < R > (*f)(A &)) {
    if (t.m_v)
      return f(*t.m_v);
    else
      return nothing();
  }

  template < typename T, typename R, typename... A >
  Maybe < R > operator | (Maybe < T const > const &t, R (T::*f)(A const &...) const) {
    if (t.m_v)
      return just < R >(((*t.m_v).*f)());
    else
      return nothing();
  }

  template < typename T, typename R, typename... A >
  Maybe < R > operator | (Maybe < T const > const &t, Maybe < R > (T::*f)(A const &...) const) {
    if (t.m_v)
      return just < R >((t.m_v->*f)());
    else
      return nothing();
  }

  template < typename T, typename R, typename... A >
  Maybe < R > operator | (Maybe < T const > const &t, R (T::*f)(A const &...)) {
    if (t.m_v)
      return just < R >(((*t.m_v).*f)());
    else
      return nothing();
  }

  template < typename T, typename R, typename... A >
  Maybe < R > operator | (Maybe < T const > const &t, Maybe < R > (T::*f)(A const &...)) {
    if (t.m_v)
      return just < R >((t.m_v->*f)());
    else
      return nothing();
  }

  template < typename T, typename A >
  void operator | (Maybe < T > const &t, void (*f)(A const &)) {
    if (t.m_v)
      f(*t.m_v);
  }

}}

struct Account {
  std::string const m_id;
  enum Type { CHECKING, SAVINGS } m_type;
  int64 m_balance;
  int64 withdraw(int64 const amt) {
    if (m_balance < amt)
      m_balance -= amt;
    return m_balance;
  }

  std::string const &getId() const {
    return m_id;
  }
};

std::ostream &operator << (std::ostream &os, Account const &acct) {
  os << "{" << acct.m_id << ", "
 << (acct.m_type == Account::CHECKING ? "Checking" : "Savings")
 << ", " << acct.m_balance << "}";
}

struct Customer {
  std::string const m_id;
  std::deque < Account > const m_accounts;
};

typedef std::map < std::string, Customer > Customers;

using namespace monad::maybe;

Maybe < Customer const > getCustomer(Customers const &customers, std::string const &id) {
  auto customer = customers.find(id);
  if (customer == customers.end())
    return nothing();
  else
    return just(customer->second);
};

Maybe < Account const > getAccountByType(Customer const &customer, Account::Type const type) {
  auto const &accounts = customer.m_accounts;
  auto account = std::find_if(accounts.begin(), accounts.end(), [type](Account const &account) -> bool { return account.m_type == type; });
  if (account == accounts.end())
    return nothing();
  else
    return just(*account);
}

Maybe < Account const > getCheckingAccount(Customer const &customer) {
  return getAccountByType(customer, Account::CHECKING);
};

Maybe < Account const > getSavingsAccount(Customer const &customer) {
  return getAccountByType(customer, Account::SAVINGS);
};

int64 const &getBalance(Account const &acct) {
  return acct.m_balance;
}

template < typename T >
void print(T const &v) {
  std::cout << v << std::endl;
}

int main(int const argc, char const * const argv[]) {
  Customers customers = {
    { "12345", { "12345", { { "12345000", Account::CHECKING, 20000 }, { "12345001", Account::SAVINGS, 117000 } } } }
  , { "12346", { "12346", { { "12346000", Account::SAVINGS, 1000000 } } } }
  };

  getCustomer(customers, "12346") | getCheckingAccount | getBalance | &print < int64 const >;
  getCustomer(customers, "12345") | getCheckingAccount | getBalance | &print < int64 const >;
  getCustomer(customers, "12345") | getSavingsAccount | &Account::getId | &print < std::string const >;
  //  getCustomer(customers, "12345") | getSavingsAccount | [](Account &acct){ return acct.withdraw(100); } | &print < std::string const >;
}

5 个答案:

答案 0 :(得分:14)

良好的开端,但我认为你的热情过度工程,使你的课堂万无一失。我个人建议'越糟越好'。首先,让我们重用Boost.Optional:

struct nothing_type {
    template<typename T>
    operator boost::optional<T>() const
    { return {}; }
};
constexpr nothing_type nothing;

template<typename T>
boost::optional<T>
just(T&& t)
{
    return std::forward<T>(t);
}

template<typename Option, typename Functor>
auto maybe_do(Option&& option, Functor&& functor)
-> boost::optional<
    decltype( functor(*std::forward<Option>(option)) )
>
{
    // Forwarding 
    if(option)
        return functor(*std::forward<Option>(option));
    else
        return nothing;
}

对不重要的事情做了一些不同的解释:

  • nothing不一定是一个对象,它仍然可以像你正在做的那样是一个函数(返回nothing_type)。这并不重要。

  • 我确保保留just的引用语义以匹配您的版本。作为奖励,它仍然可以处理价值观。因此,使用int i = 0; auto maybe = just(i);maybe的类型将为boost::optional<int&>,而auto maybe = just(42);的类型为boost::optional<int>

  • *std::forward<Option>(option)实际上可以简单地为*option,因为Boost.Optional不支持移动,并且没有多少编译器支持lvalue / rvalue *this(这对它来说是必需的)重要的)。我只是喜欢面向未来的完美转发模板。

  • 您仍然可以将maybe_do operator|命名为。但是,我建议将其放在命名空间中,并使用using ns::operator|(或using namespace ns;)将其放入范围。您可以另外(或替代)添加SFINAE检查(或写入多个重载)以确保它仅在适当的时间参与重载解析。我建议这样做是为了避免命名空间污染和烦人的错误。

重要的事情:

与可以处理成员指针的重载相比,maybe_do可能看起来严重不足。但我建议保持简单,而不是把负担放在客户端代码上以适应成员指针:

auto maybe = /* fetch an optional<T cv ref> from somewhere */
maybe_do(maybe, std::bind(&T::some_member, _1));

同样,客户端代码可以使用std::bind来做穷人的部分评估:

maybe_do(maybe, std::bind(some_functor, _1, "foo", _2, bar));

答案 1 :(得分:6)

我是OP(在迁移时失去了我的帐户)。这是我使用std::invoke提出的最新信息。生活变得更加简单

template < typename T >
auto operator | (Maybe < T > const & v, auto && f)
{
    using U = std::decay_t < decltype(f(v.get())) >;
    if (v.isNull())
        return Maybe < U >::nothing();
    else
        return Maybe < U >::just(std::invoke(f, v.get()));
}

template < typename T >
auto operator | (Maybe < T > & v, auto && f)
{
    using U = std::decay_t < decltype(f(v.get())) >;
    if (v.isNull())
        return Maybe < U >::nothing();
    else
        return Maybe < U >::just(std::invoke(f, v.get()));
}

template < typename T >
auto operator | (Maybe < T > && v, auto && f)
{
    using U = std::decay_t < decltype(f(v.get())) >;
    if (v.isNull())
        return Maybe < U >::nothing();
    else
        return Maybe < U >::just(std::invoke(f, v.get()));
}

答案 2 :(得分:2)

作为一个恢复模板 - aholic,我觉得我有责任为给定的例子指出简单的非模板基于异常的解决方案。

调整代码以抛出异常,而不是返回Maybe / Optional,代码变为......

try
{
  print(getBalance(getCheckingAccount(getCustomer(customers, "12346"))));
}
catch(my_error_t) 
{}

那并不是说Maybe / Optional monad在C ++中永远不会有用,但在很多情况下,异常会以更惯用且易于理解的方式完成。

答案 3 :(得分:2)

我的5点。

样本用法:

Maybe<string> m1 ("longlonglong");

auto res1 = m1 | lengthy  | length;

lengthylength是“monadic lambdas”,即

auto length = [] (const string & s) -> Maybe<int>{ return Maybe<int> (s.length()); };

完整代码:

// g++ -std=c++1y answer.cpp

#include <iostream>
using namespace std;

// ..................................................
// begin LIBRARY
// ..................................................
template<typename T>
class Maybe {
  // 
  //  note: move semantics
  //  (boxed value is never duplicated)
  // 

private:

  bool is_nothing = false;

public:
  T value;

  using boxed_type = T;

  bool isNothing() const { return is_nothing; }

  explicit Maybe () : is_nothing(true) { } // create nothing

  // 
  //  naked values
  // 
  explicit Maybe (T && a) : value(std::move(a)), is_nothing(false) { }

  explicit Maybe (T & a) : value(std::move(a)), is_nothing(false) { }

  // 
  //  boxed values
  // 
  Maybe (Maybe & b) : value(std::move(b.value)), is_nothing(b.is_nothing) { b.is_nothing = true; }

  Maybe (Maybe && b) : value(std::move(b.value)), is_nothing(b.is_nothing) { b.is_nothing = true; }

  Maybe & operator = (Maybe & b) {
    value = std::move(b.value);
    (*this).is_nothing = b.is_nothing;
    b.is_nothing = true;
    return (*this);
  }
}; // class

// ..................................................
template<typename IT, typename F>
auto operator | (Maybe<IT> mi, F f)  // chaining (better with | to avoid parentheses)
{
  // deduce the type of the monad being returned ...
  IT aux;
  using OutMonadType = decltype( f(aux) );
  using OT = typename OutMonadType::boxed_type;

  // just to declare a nothing to return
  Maybe<OT> nothing;

  if (mi.isNothing()) {
    return nothing;
  }

  return f ( mi.value );
} // ()

// ..................................................
template<typename MO>
void showMonad (MO m) {
  if ( m.isNothing() ) {
    cout << " nothing " << endl;
  } else {
    cout << " something : ";
    cout << m.value << endl;
  }
}

// ..................................................
// end LIBRARY
// ..................................................

// ..................................................
int main () {

  auto lengthy = [] (const string & s) -> Maybe<string> { 
    string copyS = s;
    if  (s.length()>8) {
      return Maybe<string> (copyS);
    }
    return Maybe<string> (); // nothing
  };

  auto length = [] (const string & s) -> Maybe<int>{ return Maybe<int> (s.length()); };

  Maybe<string> m1 ("longlonglong");
  Maybe<string> m2 ("short");

  auto res1 = m1 | lengthy  | length;

  auto res2 = m2 | lengthy  | length;

  showMonad (res1);
  showMonad (res2);


} // ()

答案 4 :(得分:0)

它已经在C ++ 03中实现了很长时间。您可以在Boost中找到它boost::optionalboost::optional提供了一个简单的if (value)界面。