23

I am trying to implement the Maybe monad from Haskell using the lambda functions in C++11 and templates. Here's what I have so far

#include<functional>
#include<iostream>
using namespace std;

template<typename T1>
struct Maybe
{
  T1 data;
  bool valid;
};

template<typename T1, typename T2>
Maybe<T2> operator>>=(Maybe<T1> t, std::function < Maybe<T2> (T1)> &f)
{
  Maybe<T2> return_value;
  if(t.valid == false)
  {        
    return_value.valid = false;
    return return_value;
  }
  else
  {        
    return f(t.data);
  }            
}


int main()
{
  Maybe<int> x = {5, true};
  Maybe<int> y = {29, false};

  auto z = [](int a) -> Maybe<int>
    {
      Maybe<int> s;
      s.data = a+1;
      s.valid = true;
      return s;
    };

  Maybe<int> p = (x >>= z);
  Maybe<int> q = (y >>= z);

  cout<<p.data<<' '<<p.valid<<endl;        
  cout<<q.data<<' '<<q.valid<<endl;
}    

When it comes to the actual >>= call, I am getting a compiler error saying that no match found for >>= operator. Is my understanding of C++11's lambda functions failing me here?

Xeo
  • 129,499
  • 52
  • 291
  • 397
rpg
  • 975
  • 1
  • 8
  • 18
  • 13
    Do you know [Boost.Optional](http://www.boost.org/doc/libs/1_49_0/libs/optional/doc/html/index.html)? That is C++'s `Maybe` monad. – Xeo Mar 13 '12 at 21:45
  • 1
    I do. I was trying to implement my own to understand the monads a bit. – rpg Mar 13 '12 at 21:47
  • `struct nothing {}; template using maybe = boost::variant;` – Cat Plus Plus Mar 13 '12 at 21:47
  • 6
    @rpg: C++ isn't very monad-friendly. – Cat Plus Plus Mar 13 '12 at 21:49
  • `>>=` in C++ has the wrong associativity. You'll understand the monads better if you don't have to bang your head against completely unrelated walls in the process. Try learning monads in Haskell. – R. Martinho Fernandes Mar 13 '12 at 21:50
  • 1
    @CatPlusPlus So what? Template metaprogramming isn't for the faint of heart either. And nobody said the result should be pretty. If it gives OP a better understanding of monads: mission accomplished. –  Mar 13 '12 at 21:50
  • 1
    @cat plus plus: I realize that. The question is if there is a way to get this approach to work. The types involved in the lambda seem to be fine. – rpg Mar 13 '12 at 21:52
  • @ R. Martinho Fernandes : Haskell is of course a better language to learn monads but I understand C++ better. I am trying this out so that I can understand how to write monadic code and how does this concept play out in practice. – rpg Mar 13 '12 at 21:56
  • 11
    Note that `>>=` has *nothing* to do with monads. It's an operator used in Haskell to express the bind operation for monads, but that's just a decision made by the Haskell language designers. It's not an intrinsic part of a monad that *this* particular operator is used. So there's really little point in trying to implement monads in C++ with Haskell's syntax. Implement monads in C++ in a C++-friendly way, rather than blindly copying the completely arbitrary design decisions made in Haskell – jalf Mar 13 '12 at 22:03
  • 4
    Well, sure, do it for science. But keep in mind that without a `do`-notation substitute, the way this works out in practice is... not very practical. At least please reconsider the use of `>>=`: `m >>= f >>= g` in C++ is `m >>= (f >>= g)`, but in Haskell it's `(m >>= f) >>= g`. – R. Martinho Fernandes Mar 13 '12 at 22:04
  • 1
    I used >>= simply out of familiarity. It's probably a bad choice for C++. – rpg Mar 13 '12 at 22:08
  • 1
    `>>=` is pronounced as `bind`, so that's what I suggest you to use. Also, because `return` is a C++ keyword, you can borrow the name `unit` from category theory. – Vitus Mar 13 '12 at 22:15
  • @Vitus no, it is not. It is pronounced as `bind` **in Haskell**. In C++, it is pronounced as "right-shift assignment operator", or something like that. That was precisely my point. If you want to implement a monad, you need a *bind* operation. But the *bind* operation does not need to be represented by the `>>=` operator. That association only exists in Haskell, it is not a universal requirement, or something you have to have in order for something to be a monad – jalf Mar 14 '12 at 15:36
  • Bartosz has a nice blog post about this subject: http://bartoszmilewski.com/2011/07/11/monads-in-c/ – mavam Mar 14 '12 at 17:44
  • 1
    @jalf: Of course; overloading C++'s `>>=` isn't really bright idea so I suggested a better name for the function (`bind` coming from Haskell's `>>=`). – Vitus Mar 15 '12 at 22:59
  • http://stackoverflow.com/questions/7690864/haskell-style-maybe-type-chaining-in-c11 Have a look at that one – zrb Sep 18 '14 at 02:25

6 Answers6

8

The type of a lambda isn't a specialization of std::function. It's some unamed type. There is a conversion to std::function, but that means type deduction won't work for it. So, in this call:

Maybe<int> p = (x >>= z);

The type T2 can't be deduced:

Maybe<T2> operator>>=(Maybe<T1> t, std::function < Maybe<T2> (T1)> &f)

Store the lambda in a std::function variable from the start, and it should work:

std::function < Maybe<int> (int)> z = [](int a) -> Maybe<int> { ... };

However, it's probably easier to accept any kind of function object. That way you can still use auto for the lambda.

template<typename T1, typename F>
typename std::result_of<F(T1)>::type
operator>>=(Maybe<T1> t, F&& f) {
    ... std::forward<F>(f)(t.data);
}
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • If types for T2 can't be deduced, does that mean that this approach is doomed? Is there another way to hack it without using Boost. I would like to see all the code in one place so that I can understand it better, which is why I don't want to use Boost. – rpg Mar 13 '12 at 22:00
  • Thanks a lot. That worked. But removing auto made the code a lot more verbose. Which kind of makes this impractical to use. :P – rpg Mar 13 '12 at 22:05
  • The last option works with `auto`, because it doesn't require a conversion: it deduces `F` to whatever the unnamed type of the lambda is (plus the appropriate kind of reference). – R. Martinho Fernandes Mar 13 '12 at 22:13
6

The following works for me: I use decltype to infer the type returned by the lambda:

template<typename T1, typename Func>    
auto operator>>=(Maybe<T1> t, Func f) -> decltype(f(t.data))
{    
  decltype(f(t.data)) return_value;    
  if(t.valid == false)    
  {            
    return_value.valid = false;    
    return return_value;    
  }    
  else    
  {            
    return f(t.data);    
  }                
}

EDIT

For type safety :

template<typename T1>    
struct Maybe    
{    
  T1 data;    
  bool valid;

  static const bool isMaybe = true;
};

template<typename T1, typename Func>     
auto operator>>=(Maybe<T1> t, Func f) -> decltype(f(t.data)) 
{
  typedef decltype(f(t.data)) RT;
  static_assert(RT::isMaybe, "F doesn't return a maybe");
  ...
J.N.
  • 8,203
  • 3
  • 29
  • 39
  • Thanks. This works too. But as far as I can tell, it loses a bit of type safety. I guess I shouldn't care much about this though, considering it is C++. – rpg Mar 13 '12 at 22:11
  • Not much type safety is lost and you gain in flexibility. That way you can't pass functions that don't take the wrong arguments they may just return results which are not "Maybe" which means you can convert a Maybe to a normal type if you wish to do so. – J.N. Mar 13 '12 at 22:22
4

Here's my maybe "monad" that I use quite often in my C++ projects (disclaimer: see the comments below). It's insofar more like the Haskell Maybe than your implementation as it only holds an object in the just case (points mobj on it), not wasting space if it's nothing. This also allows it to use of C++11 move semantics, to avoid unnecessary copies. The return types of fmap (fmapped member function) and >>= are deduced with decltype.

template<typename DataT>
class maybe;
template<typename DataT>
maybe<DataT> just(const DataT &obj);
struct nothing_object{nothing_object(){}};
const nothing_object nothing;

                 //template class objects of which may or may not contain some given
                // data object. Inspired by Haskell's Maybe monad.
template<typename DataT>
class maybe {
  DataT *obj;

 public:

  class iterator {
    DataT *mobj;
    explicit iterator(DataT *init):mobj(init){}
   public:
    iterator():mobj(nullptr){}
    iterator(const iterator &cp):mobj(cp.mobj){}
    bool operator!=(const iterator &other)const{return mobj!=other.mobj;}
    DataT &operator*() const{return *mobj;}
    iterator &operator++(){ mobj=nullptr; return *this; }
    friend class maybe;
  };
  class const_iterator {
    const DataT *mobj;
    explicit const_iterator(const DataT *init):mobj(init){}
   public:
    const_iterator():mobj(nullptr){}
    const_iterator(const const_iterator &cp):mobj(cp.mobj){}
    bool operator!=(const const_iterator &other)const{return mobj!=other.mobj;}
    const DataT &operator*() const{return *mobj;}
    const_iterator &operator++(){ mobj=nullptr; return *this; }
    friend class maybe;
  };
  iterator begin(){return iterator(obj);}
  iterator end(){return iterator();}
  const_iterator begin()const{return const_iterator(obj);}
  const_iterator end()const{return const_iterator();}
  const_iterator c_begin()const{return const_iterator(obj);}
  const_iterator c_end()const{return const_iterator();}

  bool is_nothing()const{return obj==nullptr;}
  void make_nothing(){delete obj; obj=nullptr;}
  bool is_just()const{return obj!=nullptr;}
  template<typename CpDataT>
  void with_just_assign(CpDataT &mdftg)const{if(obj) mdftg=*obj;}
  DataT &from_just(){return *obj;}
  DataT &operator*(){return *obj;}
  const DataT &from_just()const{return *obj;}
  const DataT &operator*()const{return *obj;}

  template<typename CmpDataT>
  bool operator==(const maybe<CmpDataT> &cmp)const{
    return is_just()==cmp.is_just() && (is_nothing() || *obj==*cmp.obj); }
  template<typename CmpDataT>
  bool operator!=(const maybe<CmpDataT> &cmp)const{
    return is_just()!=cmp.is_just() || (is_just() && *obj!=*cmp.obj); }
  bool operator==(const nothing_object &n)const{return obj==nullptr;}
  bool operator!=(const nothing_object &n)const{return obj!=nullptr;}

  template<typename MpFnT>
  auto fmapped(MpFnT f) const -> maybe<decltype(f(*obj))> {
    return obj? just(f(*obj)) : nothing;                  }
  template<typename MonadicFn>
  auto operator>>=(MonadicFn f) const -> decltype(f(*obj)) {
    return obj? f(*obj) : nothing;                         }
  template<typename ReplaceDT>
  auto operator>>(const maybe<ReplaceDT> &r) const -> maybe<ReplaceDT> {
    return obj? r : nothing;                                           }
  auto operator>>(const nothing_object &n) const -> maybe<DataT> {
    return nothing;                                              }


  maybe(const nothing_object &n):obj(nullptr){}
  template<typename CpDataT>
  explicit maybe(const CpDataT &cobj):obj(new DataT(cobj)){}
  template<typename CpDataT>
  maybe &operator=(const CpDataT &cobj){delete obj; obj=new DataT(cobj); return *this;}
  template<typename CpDataT>
  maybe(const maybe<CpDataT> &cp):obj(cp.is_just()?new DataT(cp.from_just()):nullptr){}
  template<typename CpDataT>
  maybe &operator=(const maybe<CpDataT> &cp){
    delete obj;  obj = cp.is_just()? new DataT(cp.from_just()) : nullptr; return *this;}
  maybe(maybe<DataT> &&mv):obj(mv.obj){mv.obj=nullptr;}
  maybe &operator=(maybe<DataT> &&mv) {
    delete obj; obj=mv.obj; mv.obj=nullptr; return *this; }

  ~maybe(){delete obj;}
};

template<typename DataT>
auto just(const DataT &obj) -> maybe<DataT> {return maybe<DataT>(obj);}

template<typename MpFnT, typename DataT>              // represents Haskell's <$> infix
auto operator^(MpFnT f, const maybe<DataT> &m) -> decltype(m.fmapped(f)) {
  return m.fmapped(f);
}

template<typename DataT>
auto joined(const maybe<maybe<DataT>> &m) -> maybe<DataT> {
  return m.is_just()? m.from_just() : nothing;
}


template<typename DataT>
auto maybe_yes(const std::pair<DataT,bool>& mbcst) -> maybe<DataT> {
  return mbcst.second ? just(mbcst.first) : nothing;
}
template<typename DataT>
auto maybe_not(const std::pair<DataT,bool>& mbcst) -> maybe<DataT> {
  return !mbcst.second ? just(mbcst.first) : nothing;
}

The somewhat strange-seeming begin and end iterators allow it to be used in C++11 range-based for loops:

maybe<int> a = just(7), b = nothing;

for (auto&i: a) std::cout << i;
for (auto&i: b) std::cout << i;

outputs only once 7.

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • 1
    +1 for the interesting code, but it could be presented a bit better. – J.N. Mar 13 '12 at 23:34
  • 2
    You didn't note that not trading space comes at the (not neglectable) expense of dynamic memory allocation. Any reason not to use `unique_ptr` ? – J.N. Mar 13 '12 at 23:37
  • @J.N. indeed, dynamic memory allocation is much more expensive than stack, so it's a bad idea to use it for many small objects. I don't claim it's the optimal implementation, I just had the idea to implement it (for a class that had rather a lot of data members, so there it was good to allocate on heap) when I started learning about the C++0x features and wanted to learn about implementing move semantics. And I first wanted to know how it works with plain pointers, now I'd use `unique_ptr`, arguably better choice here. – leftaroundabout Mar 14 '12 at 01:04
3

Noticed that std::function have an empty state, we can have the following implementation

template<typename T>
class Maybe{
private:
    Maybe(T t){
        get = [t](){ return t; };
    }
    Maybe(){}
    std::function<T ()> get;
public:
    typedef T content_type;

    template<typename WhenJust, typename WhenNothing>
    auto on(WhenJust &&whenJust, WhenNothing &&whenNothing) 
        -> decltype(whenNothing()){
        if(get==nullptr) return whenNothing();
        else return whenJust(get());
    }
    template<typename U>
    friend Maybe<U> just(U u);
    template<typename U>
    friend Maybe<U> nothing();
};

template<typename T>
Maybe<T> just(T t){
    return Maybe<T>(t);
}

template<typename T>
Maybe<T> nothing(){
    return Maybe<T>();
}

template<typename T, typename BinderFunction>
auto operator >>(Maybe<T> m, BinderFunction bind) 
    -> Maybe<typename decltype(bind(*((T*)nullptr)))::content_type> {
    return m.on([bind](T v){
        return bind(v);
    },[](){
        return nothing<typename decltype(bind(*((T*)nullptr)))::content_type>();
    });
}

In this implementation, all factory methods are free (friend) functions, the >> operator (not to be confused with >> in Haskell, this is the equivalent of >>= with same associative) is also free, and even not a friend function. Also notice the on member function, this is used to force any client intended to use a Maybe instance must be prepared for both cases (Just or Nothing).

Here is an example of usage:

int main()
{
    auto n = just(10) >> [](int j){ std::cout<<j<<" >> "; return just(j+10.5); }
        >> [](double d){ std::cout<<d<<" >> "; return nothing<char>(); }
        >> [](char c){ std::cout<<c; return just(10); }
        ;

    n.on(
        [](int i) { std::cout<<i; },
        []() { std::cout<<"nothing!"; });

    std::cout << std::endl;
    return 0;
}

The output is

10 >> 20.5 >> nothing!
Earth Engine
  • 10,048
  • 5
  • 48
  • 78
1

My 5 cts.

Sample usage:

Maybe<string> m1 ("longlonglong");

auto res1 = m1 | lengthy  | length;

lengthy and length are "monadic lambdas", i.e.

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

Complete code:

// 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);


} // ()
cibercitizen1
  • 20,944
  • 16
  • 72
  • 95
0

Literally copy & pasting from Haskell style "Maybe" type & *chaining* in C++11

This is probably what you really want to achieve

#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 >;
}
Community
  • 1
  • 1
zrb
  • 721
  • 1
  • 6
  • 15