问题描述
我反复发现自己需要Haskell风格可能
(特别是Maybe链)在我的项目中工作。例如。提取客户的请求,我们给予客户ID ...查找客户在缓存...如果客户找到...查找她的储蓄帐户...如果有一个帐户...撤回...在任何点在这个链中,如果有查找失败,什么也不做,并返回失败。
I repeatedly find myself requiring Haskell style Maybe
(especially Maybe chaining) in my project at work. E.g. withdrawal request from customer and we are given the customer ID... lookup customer in cache... if customer is found... lookup her savings account... if there is an account... withdraw... At any point in this chain, if there is a lookup failure, do nothing and return a failure.
我的链是大的...有时只要6 ...所以这里是我滑动在 Haskell.Data.Maybe
在C ++ 0x ...(注意...这应该在C + +如果我停止使用可变参数模板工作)。我已经制定了链接自由功能接受一个参数或成员函数不接受参数,我很高兴的接口。然而,对于函数采取多个参数...我必须写一个lambda函数来模拟部分应用程序。有办法避免吗?请参见 main()
的最后一行。即使它是uncommented它不会编译,但对于const /非const混合。但是问题仍然存在。
My chains are large... sometimes as long as 6... so here is my swipe at Haskell.Data.Maybe
in C++0x... (note... this should work in C++ if I stop using variadic templates). I have worked out chaining for free-functions taking one argument or member functions taking no arguments and I am happy with the interface. However, for functions taking multiple parameters... I have to write a lambda function to simulate partial application. Is there a way to avoid it? See the last line of main()
. Even if it is uncommented it won't compile, but for const/non-const mixing. But the question still stands.
对于大块的代码,我很抱歉...我希望这不会转移对其他人可能感兴趣的人...
Sorry about the large chunk of code... I hope this wouldn't turn away people who might otherwise be interested in this...
#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 >;
}
推荐答案
你在你的热情过度设计,使你的阶级万无一失。个人我建议更糟的是更好。首先,让我们重用Boost.Optional:
Good start, but I think you're over-engineering in your zeal to make your class foolproof. Personally I'd recommend 'worse is better'. First, let's reuse 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;
}
对不太重要的事情的一些解释:
Some various explanations on things that aren't really important:
-
没有
不必是一个对象,它仍然可以是一个函数(返回nothing_type
)。这不重要。
nothing
doesn't have to be an object, it can still be a function (returningnothing_type
) like you're doing. That's not important.
我确保保留的引用语义只是
匹配您的版本。然而,作为一个奖励,它仍然可以处理价值观。因此, int i = 0; auto也许= just(i);
那么的类型可能将会
boost :: optional< int&
,而 auto maybe = just(42);
是 boost :: optional< int>
。
I made sure to preserve the reference semantics of just
to match your version. As a bonus though, it can still deal with values. As such, with int i = 0; auto maybe = just(i);
then the type of maybe
will be boost::optional<int&>
, whereas with auto maybe = just(42);
it is boost::optional<int>
.
* std :: forward< Option>(option)
be * option
因为Boost.Optional不支持移动,并且很多编译器支持lvalue / rvalue * this
需要它是重要的)。
the *std::forward<Option>(option)
can actually simply be *option
as Boost.Optional is not move-aware and not many compilers support lvalue/rvalue *this
(which would be needed for it to matter). I just like future-proofing perfect-forwarding templates.
您仍然可以输入 maybe_do
operator |
。然而,我建议把它放在一个命名空间,并使用使用ns :: operator |
(或使用命名空间ns;
)将其纳入范围。您可以另外(或替代地)添加SFINAE检查(或写几个重载),以确保它只在适当的时间参与重载分辨率。我建议避免命名空间污染和恼人的错误。
you can still name maybe_do
operator|
instead. I would however recommend putting it in a namespace and use using ns::operator|
(or using namespace ns;
) to put it into scope. You can additionally (or instead) add an SFINAE check (or write several overloads) to make sure it only participates in overload resolution at appropriate times. I'm advising this to avoid namespace pollution and annoying errors.
重要的东西:
与可以处理成员指针的重载相比, maybe_do
可能看起来严重不足。但是我建议保持简单,而是把负担放在客户端代码以适应成员指针:
It may look like maybe_do
is severely underpowered compared to your overloads that can deal with member pointers. But I'd recommend keeping it simple and instead putting the burden on client-code to adapt member pointers:
auto maybe = /* fetch an optional<T cv ref> from somewhere */
maybe_do(maybe, std::bind(&T::some_member, _1));
类似的客户端代码可以使用 std :: bind
做穷人的部分评估:
Similarly client code can use std::bind
to do the poor man's partial evaluation:
maybe_do(maybe, std::bind(some_functor, _1, "foo", _2, bar));
这篇关于Haskell风格“可能”类型& * chaining * in C ++ 11的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!