Alex Rivera | Logout

Haskell style "Maybe" type & *chaining* in C++11

Asked 2011-10-07T17:40:07.443
31

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.

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() 
Edit
Report

1 Answer

15

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 doesn't have to be an object, it can still be a function (returning nothing_type) like you're doing. That's not important.

  • 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>.

  • 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.

  • 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 i

answered 2011-10-07T19:07:55.193

Your Answer