compiler - c++14
Haskell estilo "Maybe" tipo &*encadenamiento*en C++ 11 (5)
Buen comienzo, pero creo que tienes demasiada ingeniería para hacer que tu clase sea infalible. Personalmente recomendaría ''peor es mejor''. En primer lugar, vamos a reutilizar 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;
}
Algunas explicaciones sobre cosas que no son realmente importantes:
nothing
no tiene que ser un objeto, todavía puede ser una función (que no devuelvenothing_type
) como lo está haciendo. Eso no es importante.Me aseguré de preservar la semántica de referencia
just
para que coincida con su versión. Sin embargo, como bonificación, todavía puede tratar con valores. Como tal, conint i = 0; auto maybe = just(i);
int i = 0; auto maybe = just(i);
entonces el tipo demaybe
seráboost::optional<int&>
, mientras que conauto maybe = just(42);
esboost::optional<int>
.La
*std::forward<Option>(option)
realidad puede ser simplemente*option
ya que Boost.Optional no es sensible al movimiento y no hay muchos compiladores compatibles con lvalue / rvalue*this
(esto sería necesario para que sea importante). Simplemente me gustan las plantillas de reenvío perfecto para el futuro.todavía puedes nombrar
maybe_do
operator|
maybe_do
operator|
en lugar. Sin embargo, recomendaría ponerlo en un espacio de nombres y usarusing ns::operator|
(ousing namespace ns;
) para ponerlo en el alcance. Además, puede (o en su lugar) agregar un cheque SFINAE (o escribir varias sobrecargas) para asegurarse de que solo participa en la resolución de sobrecarga en los momentos apropiados. Estoy aconsejando esto para evitar la contaminación del espacio de nombres y los errores molestos.
Lo importante:
Puede parecer que maybe_do
tiene una potencia muy maybe_do
comparación con las sobrecargas que pueden manejar los punteros de los miembros. Sin embargo, recomiendo que sea sencillo y, en lugar de eso, ponga la carga en el código del cliente para adaptar los punteros de los miembros:
auto maybe = /* fetch an optional<T cv ref> from somewhere */
maybe_do(maybe, std::bind(&T::some_member, _1));
De manera similar, el código de cliente puede usar std::bind
para hacer la evaluación parcial del hombre pobre:
maybe_do(maybe, std::bind(some_functor, _1, "foo", _2, bar));
En repetidas ocasiones me encuentro a mí mismo requiriendo el estilo Haskell Maybe
(especialmente el encadenamiento) en mi proyecto en el trabajo. Por ejemplo, la solicitud de retiro del cliente y nos dan la ID del cliente ... buscar al cliente en el caché ... si se encuentra al cliente ... buscar en su cuenta de ahorros ... si hay una cuenta ... retirar ... En cualquier momento punto en esta cadena, si hay un error de búsqueda, no haga nada y devuelva un error.
Mis cadenas son grandes ... a veces tan largas como 6 ... así que aquí está mi golpe en Haskell.Data.Maybe
en C ++ 0x ... (nota ... esto debería funcionar en C ++ si dejo de usar plantillas variadic ). He desarrollado el encadenamiento para funciones libres tomando un argumento o funciones miembro sin tomar argumentos y estoy contento con la interfaz. Sin embargo, para las funciones que toman parámetros múltiples ... Tengo que escribir una función lambda para simular una aplicación parcial. ¿Hay alguna forma de evitarlo? Ver la última línea de main()
. Incluso si no está comentado, no se compilará, pero para la mezcla constante / no constante. Pero la pregunta sigue en pie.
Lo siento por la gran cantidad de código ... Espero que esto no rechace a las personas que de otra manera podrían estar interesadas en esto ...
#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 >;
}
Como un adicto a la recuperación de plantillas, siento que es mi deber señalar la solución simple basada en excepciones sin plantillas para el ejemplo dado.
Ajuste el código para lanzar una excepción en lugar de devolver Maybe / Optional, y el código se convierte en ...
try
{
print(getBalance(getCheckingAccount(getCustomer(customers, "12346"))));
}
catch(my_error_t)
{}
Eso no quiere decir que las mónadas Maybe / Optional nunca sean útiles en C ++, pero en muchos casos las excepciones harán que las cosas se realicen de una manera mucho más idiomática y fácil de entender.
Mis 5 cts.
Uso de muestra:
Maybe<string> m1 ("longlonglong");
auto res1 = m1 | lengthy | length;
lengthy
y length
son "lambdas monádicas", es decir
auto length = [] (const string & s) -> Maybe<int>{ return Maybe<int> (s.length()); };
Código completo:
// 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);
} // ()
Se ha implementado en C ++ 03 durante mucho tiempo. Lo puedes encontrar en Boost as boost::optional
. boost::optional
ofrece una interfaz simple if (value)
.
Yo era el OP (perdí mi cuenta cuando SO migró). Aquí está lo último que se me ocurrió usando std::invoke
. La vida se vuelve mucho más simple.
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()));
}