Monadic lifting and

slot-based programming can be combined in pleasant ways. The key idea is: if

T is a type with a slot-based interface then a monadic type

M<T> can easily replicate

T's interface by appropriate lifting; for instance, this is the

Maybe monad with slot compatibility:

template<template<typename> class M,typename T>
struct slot_monad
{
template<typename Slot,typename ...Args>
auto operator()(Slot&& s,Args&&... args)const
{
return mlift<M>(std::forward<Slot>(s))(
static_cast<const M<T>&>(*this),
std::forward<Args>(args)...);
}
template<typename Slot,typename ...Args>
auto operator()(Slot&& s,Args&&... args)
{
return mlift<M>(std::forward<Slot>(s))(
static_cast<M<T>&>(*this),
std::forward<Args>(args)...);
}
};
template<typename T>
struct maybe:boost::optional<T>,**slot_monad<maybe,T>**
{
// the rest of the interface, including >>= overloads,
// remains the same
};

slot_monad<M> provides the required magic. How does it work? Remember that slots are *functors*, and as such can be functionally transformed, and, in our case, lifted to the monadic realm of M; so, M<T> can replicate T's interface by reacting to any slot s with a corresponding call to mlift<M>(s). M<T> effectively becomes a drop-in replacement of T (with all the added semantics brought in by M).

Let us see this in action. All the examples in this entry are gathered into a

program runnable in a C++11 environment with C++14 automatic return type detection (such as GCC 4.8 with

-std=c++1y). Boost is also needed.

**Dealing with optional objects**

vector is the slot-based replica of

std::vector we already studied in a

previous entry. both

vector and

maybe has been equipped with iostream

operator<< overloads for testing purposes.

maybe<vector<int>> v=vector<int>{};
for(int i=0;i<100;++i)v(push_back,i);
std::cout<<mlift<maybe>(accumulate())(v(begin),v(end),0)<<"\n";
**Output: 4950**

maybe<vector> behaves as a regular (slot-based) vector and accepts push_back without problems. Using standard algorithms on this class is, however, a bit more complicated: std::acummulate(v(begin),v(end),0) wouldn't work because v(begin) and v(end) return maybe<vector::iterator>s, which are *not* iterators —iterator interfaces, based on operator*, operator++, etc., are not easly translatable to slot member functions. So, we have to lift the algorithm, for which we need a polymorphic version of it:

struct accumulate
{
template<class InputIterator, class T>
T operator()(
InputIterator first,InputIterator last,T init)const
{
return std::accumulate(first,last,init);
}
template<class InputIterator,class T,class BinaryOperation>
T operator()(
InputIterator first,InputIterator last,T init,
BinaryOperation binary_op)const
{
return std::accumulate(first,last,init,binary_op);
}
};

**Dealing with Nothing**

As guaranteed by the Maybe monad, passing Nothing just works: member functions simply do not do anything and return Nothing.

maybe<vector<int>> v=boost::none;
for(int i=0;i<100;++i)v(push_back,i);
std::cout<<mlift<maybe>(accumulate())(v(begin),v(end),0)<<"\n";
**Output: none**

**Optional member functions**

Duality between slot-based classes and slots allows us to pass monadic types also as slots.

maybe<vector<int>> v=vector<int>{};
for(int i=0;i<100;++i)v(maybe<push_back_s>{},i);
std::cout<<mlift<maybe>(accumulate())(v(begin),v(end),0)<<"\n";
**Output: 0**

Here, we use a maybe<push_back_s> (push_back_s being the type of push_back) which happens to be Nothing, so the vector is not added any element.

**Introducing the List monad**

This monad maintains a collection of objects of the underlying type; binding to

f consists in invoking repeatedly

f for each of the objects and gathering the results into another list:

template<typename T>
struct mlist:std::list<T>,slot_monad<mlist,T>
{
using super=std::list<T>;
using super::super;
mlist(const T& t){super::push_back(t);}
mlist(T&& t){super::emplace_back(std::move(t));}
};
template<typename T,typename F>
auto operator>>=(const mlist<T>& m,F&& f)
{
decltype(f(std::declval<T>())) ret={};
for(const auto& t:m)ret.splice(ret.end(),f(t));
return std::move(ret);
}
template<typename T,typename F>
auto operator>>=(mlist<T>& m,F&& f)
{
decltype(f(std::declval<T>())) ret={};
for(auto& t:m)ret.splice(ret.end(),f(t));
return std::move(ret);
}

mlist also has an iostream operator<< that we don't show here. This monad allows us then to carry out simultaneous operations on a collection of objects:

mlist<vector<int>> ml={{0,1},{1,2}}; // #1
std::cout<<ml(size)<<"\n";
ml(push_back,1); // #2
std::cout<<ml<<"\n";
ml([](vector<int>& v){v(push_back,v(at,0));}); // #3
std::cout<<ml<<"\n";
**Output:
{2,2}
{{0,1},{1,2}}
{{0,1,1},{1,2,1}}**

In the use case #1, the results of size (std::size_ts) are compiled into an mlist<std::size_t> of their own. Use case #3 is interesting: much as we can pass any slot to mlist<vector<int>>, we can in fact pass any functor accepting vector<int> as its first parameter, such as the lambda function shown in the example. This property is exhibited by any *open-ended* slot-based class, that is, a class *C* that accepts any functor *F* as a permissible slot and invokes it for some underlying object *O*(*C*):

*C*(*S*,...) → *S*(*O*(*C*),...).

(As an effect of the duality pervading slot-based programming, plain slots such as push_back are in their turn open-ended classes.)

**mlist and maybe**

Different monadic types can be used jointly without surprises, as each contributes its own lifting. In the following example we deal with a list of potentially non-existent vector<int>s.

mlist<maybe<vector<int>>> ml=
{vector<int>{0,1},vector<int>{2,3},boost::none}; // #1
std::cout<<ml(size)<<"\n";
ml(push_back,1); // #2
std::cout<<ml<<"\n";
ml([](maybe<vector<int>>& v){v(push_back,v(at,0));}); // #3
std::cout<<ml<<"\n";
ml(push_back,mlist<int>{5,6,7,8}); // #4
std::cout<<ml<<"\n";
**Output:
{2,2,none}
{{0,1,1},{2,3,1},none}
{{0,1,1,0},{2,3,1,2},none}
{{0,1,1,0,5,6,7,8},{2,3,1,2,5,6,7,8},none}**

Use case #4 shows how lifted member functions accept monadic types in any argument position, not only for the first one: pushing back an mlist<int> is equivalent to pushing back each of its elements one by one.

**Lists of commands**

Remember that slots, by their being esentially functors, can be used with global function syntax (i.e. push_back(v) rather than v(push_back)). Combining this property with mlist allows us to work with collections of commands on collections of objects:

using namespace std::placeholders;
mlist<maybe<vector<int>>> ml=
{vector<int>{0,1},vector<int>{2,3},boost::none};
mlist<std::function<maybe<mvoid>(maybe<vector<int>>&)>>
commands=
{
pop_back,
std::bind(push_back,_1,0)
};
commands(ml);
std::cout<<ml<<"\n";
**Output: {{0,0},{2,0},none}**

The example also shows how slots can be bound much as any other regular functor.

**Lifted functions on non-monadic objects**

vector<int> v;
mlift<mlist>(push_back)(v,mlist<int>{0,1,2,3,4});
std::cout<<v<<"\n";
**Output: {0,1,2,3,4}**

vector<int> knows nothing about mlist, so push_back(v,mlist<int>{0,1,2,3,4}) woud not work. The problem is easily solved by lifting push_back.

**Conclusions**

Slot-based programming makes it immediate for monadic types to replicate the interfaces of their underlying types via lifting, which can be exploited to use a monad M<T> as a syntactically compatible replacement of T, with only small adjustments needed in some cases. Slot-class duality and joint use of different monadic realms combine in surprisingly flexible ways that allow for interesting behavioral transformations of non-monadic programs.