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.

## No comments :

## Post a Comment